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 ) }