Issue #288
selectionIndicatorImage
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import UIKitclass 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import UIKitclass 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) } }
In iOS 13, we need to use viewDidAppear
1 2 3 4 5 override func viewDidAppear (_ animated: Bool) { super .viewDidAppear(animated) moveIndicator(index: selectedIndex, animated: false ) }