Home > Software engineering >  Navigation to/from Tab Bar Controller
Navigation to/from Tab Bar Controller

Time:03-20

EDIT - Simplified version:

If I go from a ViewController to a Tab Bar Controller via a Button, how do I get back to the ViewController? How do I (can I?) add a button to the Tab Bar to do so?

(the top half of the below image along with the Tab Bar menu shown below that)


I want to navigate between a View Controller and different Tab Bar Controllers.

Ideally, this would be done with a "Main Menu" button on each of the Tab Bar Controllers.

enter image description here

So in this example, each of the Tab Bar Controllers would have three buttons like this instead of the two shown above.

enter image description here

CodePudding user response:

  • Create three buttons on each tab controller (name one Main Menu)- make the main menu controller blank
  • Create (and name) relationship segues between the Main Menu blank view controllers (by holding the control button and dragging a relationship between the Main Menu controllers and the main VC, and do present modally, over view controller)
  • Link both Main Menu tabs to a standard Cocoapods storyboard file, and on view did load, perform the segue like so:

self.performSegue(withIdentifier: "name", sender: self)

CodePudding user response:

I have a couple of options for you to accomplish this.

The simplest and perhaps the right way would be embed your main menu inside a UINavigationController and then push to either of your UITabBarControllers

That would give you a back button for free to your main menu on the navigation bar. I recommend this approach.

UINavigationController with UITabBarController Swift iOS

However, if you don't want to / cannot use a UINavigationController and / or want to get this functionality by having an extra main menu button on your tab bar, here is a workaround:

  1. Subclass the UITabBarController
  2. Override the viewControllers property and observe when it gets set
  3. Once it is set, add an extra view controller at the beginning
  4. Observe when you tap on the first tab and go back to the main menu view controller

To get this working, here is a small demo I created.

First, I created 4 random view controllers to serve as the view controllers inside my tab bar controller

fileprivate class PurpleVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .purple
        title = "Purple"
    }
}

fileprivate class WhiteVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = "White"
    }
}

fileprivate class RedVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        title = "Red"
    }
}

fileprivate class YellowVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .yellow
        title = "Yellow"
    }
}

Then I subclassed the UITabBarController to get my custom functionality

class CustomTabBarController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // This is needed to observe which tab was tapped
        delegate = self
    }
    
    // This is needed to prevent a loop when adding the dummy view controller
    private var isConfiguringTabs = false
    
    override var viewControllers: [UIViewController]? {
        didSet {
            addMenuVC()
            isConfiguringTabs = false
        }
    }
    
    private func addMenuVC() {
        if !isConfiguringTabs {
            isConfiguringTabs = true
            
            let menuViewController = UIViewController()
            
            if let currentVC = viewControllers?.first {
                menuViewController.view.backgroundColor
                    = currentVC.view.backgroundColor
                
            }
        
            menuViewController.title = "Main Menu"
            
            // Add a dummy view controller so that we get the
            // first tab
            viewControllers?.insert(menuViewController, at: 0)
        }
    }
}

extension CustomTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController,
                          didSelect viewController: UIViewController) {
        // If you select the first tab, go back to the main menu
        if tabBarController.selectedIndex == 0 {
            dismiss(animated: true, completion: nil)
            return
        }
        
        // Otherwise just change the bg color of the first view controller
        // to the current view controller's background which gives it a
        // better transition when going back to the main menu
        if let menuVC = viewControllers?.first,
           let currentVC = viewControllers?[tabBarController.selectedIndex] {
            menuVC.view.backgroundColor = currentVC.view.backgroundColor
        }
    }
}

Then I create the main menu view controller and make use of this custom tab bar controller when tapping on a button

class MainMenuVC: UIViewController {
    
    let buttonA = UIButton(type: .system)
    let buttonB = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Main Menu"
        view.backgroundColor = .white
        configureButtonA()
        configureButtonB()
    }
    
    private func configureButtonA() {
        
        buttonA.translatesAutoresizingMaskIntoConstraints = false
        buttonA.setTitle("Button A", for: .normal)
        buttonA.setTitleColor(.white, for: .normal)
        buttonA.backgroundColor = .systemBlue
        buttonA.addTarget(self, action: #selector(buttonATapped),
                          for: .touchUpInside)
        view.addSubview(buttonA)
        
        view.addConstraints([
        
            buttonA.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            buttonA.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            buttonA.widthAnchor.constraint(equalToConstant: 100),
            buttonA.heightAnchor.constraint(equalToConstant: 100)
        
        ])
    }
    
    private func configureButtonB() {
        
        buttonB.translatesAutoresizingMaskIntoConstraints = false
        buttonB.setTitle("Button B", for: .normal)
        buttonB.setTitleColor(.white, for: .normal)
        buttonB.backgroundColor = .red
        buttonB.addTarget(self, action: #selector(buttonBTapped),
                          for: .touchUpInside)
        view.addSubview(buttonB)
        
        view.addConstraints([
        
            buttonB.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            buttonB.topAnchor.constraint(equalTo: buttonA.bottomAnchor,
                                         constant: 40),
            buttonB.widthAnchor.constraint(equalToConstant: 100),
            buttonB.heightAnchor.constraint(equalToConstant: 100)
        
        ])
    }
    
    private func presentTabController(with viewControllers: [UIViewController]) {
        let customTabController = CustomTabBarController()
        customTabController.viewControllers = viewControllers
        customTabController.modalPresentationStyle = .fullScreen
        present(customTabController, animated: true, completion: nil)
    }
    
    
    @objc
    private func buttonATapped() {
        presentTabController(with: [WhiteVC(), PurpleVC()])
    }
    
    @objc
    private func buttonBTapped() {
        presentTabController(with: [YellowVC(), RedVC()])
    }
}

And finally, since I don't use storyboards I instantiate the main menu view controller from the scene delegate which you don't need to do

So this gives me the following experience with an additional tab in the beginning that goes back to the main menu when tapped

Custom UITabBarController custom tab button to navigate back to previous view controller swift iOS

  • Related