How to add indicator under tab bar buttons in iOS
Tags: ios, swift, indicator, tabbar
Published: 2019-06-11 13:31:59

---
title: How to add indicator under tab bar buttons in iOS
published: true
description: 
tags: ios,swift,indicator,tabbar
---

## selectionIndicatorImage

- https://developer.apple.com/documentation/uikit/uitabbar/1623456-selectionindicatorimage
- Should design image with enough height, transparent background and indicator at the bottom

> Use this property to specify a custom selection image. Your image is rendered on top of the tab bar but behind the contents of the tab bar item itself. The default value of this property is nil, which causes the tab bar to apply a default highlight to the selected item

## Custom UITabBar or UITabBarController

- Hide existing `tabBar` `tabBar.isHidden = true`
- Position custom buttons

```swift
import UIKit

class CustomTabBarController: UITabBarController {
    private let bar = UIView()
    private var buttons = [UIButton]()

    override var viewControllers: [UIViewController]? {
        didSet {
        	populate(viewControllers: viewControllers)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        setup()
    }

    private func setup() {
        tabBar.isHidden = true
        bar.backgroundColor = R.color.background
        view.addSubview(bar)
        NSLayoutConstraint.on([
            bar.leftAnchor.constraint(equalTo: view.leftAnchor),
            bar.rightAnchor.constraint(equalTo: view.rightAnchor),
            bar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
			bar.topAnchor.constraint(equalTo: tabBar.topAnchor)
        ])
    }

    private func populate(viewControllers: [UIViewController]) {
        buttons.forEach {
            $0.removeFromSuperview()
        }

        buttons = viewControllers.map({
            let button = UIButton()
            button.setImage($0.tabBarItem.image, for: .normal)
            bar.addSubview(button)
            return button
        })

        view.setNeedsLayout()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        let padding: CGFloat = 20
        let buttonSize = CGSize(width: 30, height: 44)
        let width = view.bounds.width - padding * 20

        for (index, button) in buttons.enumerated() {
            button.center = CGPoint(x: bar.center.x, y: bar.frame.height/2)
        }
    }
}
```

## Handle UITabBarControllerDelegate

- Override `func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)`
- `tabBar.subviews` contains 1 private `UITabBarBackground` and many private `UITabBarButton`

```swift
import UIKit

class CustomTabBarController: UITabBarController {
    let indicator: UIView = {
        let view = UIView()
        view.backgroundColor = R.color.primary
        view.frame.size = CGSize(width: 32, height: 2)
        view.layer.cornerRadius = 1

        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        tabBar.addSubview(indicator)
        self.delegate = self
    }

    private func animate(index: Int) {
        let buttons = tabBar.subviews
            .filter({ String(describing: $0).contains("Button") })

        guard index < buttons.count else {
            return
        }

        let selectedButton = buttons[index]
        UIView.animate(
            withDuration: 0.25,
            delay: 0,
            options: .curveEaseInOut,
            animations: {
                let point = CGPoint(
                    x: selectedButton.center.x,
                    y: selectedButton.frame.maxY - 1
                )

                self.indicator.center = point
            },
            completion: nil
        )
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        animate(index: selectedIndex)
    }
}

extension CustomTabBarController: UITabBarControllerDelegate {
    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        guard
            let items = tabBar.items,
            let index = items.firstIndex(of: item)
        else {
            return
        }

        animate(index: index)
    }
}
```

Original post https://github.com/onmyway133/blog/issues/288