Search code examples
swiftuinavigationcontrolleruitabbarcontroller

Navigate programmatically inside a custom UITabBarController (Swift)


I'm using a custom UITabBarController based on Pavel Bogart solution : quite simple, useful and perfect for what I need to do. Unless for 1 thing I don't know how to manage : How to switch programmatically from 1 VC to another (so, without touch tabBar items).

I tried some funcs with selectedIndex = X, modalPresentationStyle = .fullScreen, but nothing really concrete that can be done exactly as we just touch items on UITabBarController.

Here is the code :

class MainTabBarController: UITabBarController {

override func viewDidLoad() {
    super.viewDidLoad()
    setupTabBar()

}

var TabControllers = [VC1(), VC2(), VC3(), VC4(), VC5()]

func setupTabBar(){
    tabBar.backgroundColor = Theme.navBarBackgroundColor
    tabBar.isTranslucent = true
    tabBar.clipsToBounds = true
    
    let screen1 = createNavController(vc: TabControllers[0], selected: #imageLiteral(resourceName: "img1on"), unselected: #imageLiteral(resourceName: "img1off"), label: "Name1")
    let screen2 = createNavController(vc: TabControllers[1], selected: #imageLiteral(resourceName: "img2on"), unselected: #imageLiteral(resourceName: "img2off"),  label: "Name2")
    let screen3 = createNavController(vc: TabControllers[2], selected: #imageLiteral(resourceName: "img3on"), unselected: #imageLiteral(resourceName: "img3off"),  label: "Name3")
    let screen4 = createNavController(vc: TabControllers[3], selected: #imageLiteral(resourceName: "img4on"), unselected: #imageLiteral(resourceName: "img4off"),  label: "Name4")
    let screen5 = createNavController(vc: TabControllers[4], selected: #imageLiteral(resourceName: "img5on"), unselected: #imageLiteral(resourceName: "img5off"),  label: "Name5")
    
    viewControllers = [screen1, screen2, screen3, screen4, screen5]
    
    guard let items = tabBar.items else { return }
    for item in items {
        let unselectedItem = [NSAttributedString.Key.foregroundColor: Theme.darkTextColor]
        let selectedItem = [NSAttributedString.Key.foregroundColor: Theme.mainAccentColor]
        let attributes = [NSAttributedString.Key.font: UIFont(name: Theme.subTitle, size: 16)]
        item.setTitleTextAttributes(attributes as [NSAttributedString.Key : Any], for: .normal)
        item.setTitleTextAttributes(attributes as [NSAttributedString.Key : Any], for: .selected)
        item.setTitleTextAttributes(unselectedItem as [NSAttributedString.Key : Any], for: .normal)
        item.setTitleTextAttributes(selectedItem as [NSAttributedString.Key : Any], for: .selected)
    }
}

 override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    AppUtility.lockOrientation(.portrait)

}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    AppUtility.lockOrientation(.all)
}

}

And its extension :

extension UITabBarController {

func createNavController(vc: UIViewController, selected: UIImage, unselected: UIImage, label: String) -> UINavigationController {
    let viewController = vc
    let navController = UINavigationController(rootViewController: viewController)
    navController.tabBarItem.image = unselected
    navController.tabBarItem.selectedImage = selected
    navController.tabBarItem.title = label
    return navController
}

}

Tell me what you think about it.


Solution

  • It's so weird that selectedIndex didn't work for you, I did a similar solution and that worked for me.

    Try having the correct references to your root controller ( your UITabBarController in this case ) and the child controllers, keeping them alive with a strong reference.

    I have done a simple project to demonstrate it.

    Programmatic TabBarController repo

    I Hope I Helped!