Search code examples
iosswiftuivewcontroller

Can you call addChild multiple times on UIViewController?


I'm working with a UIViewController and have two main displays of type UIViewController that I want to programmatically switch between. In order to do this I called addChild() two separate times upon instantiating the parent view controller.

Then later, when I want to switch between them I simply modify the isHidden property of each of them.

However, I believe this is causing some unintended behavior upon launch, where the UIViewController that is added first cannot be displayed without first displaying the UIViewController that's added second.

Doing research I couldn't find examples of people calling addChild() multiple times, and I was wondering if this is common practice or if there is a more common approach that might eliminate room for error. The documentation for addChild() says nothing about calling it multiple times. I'm quite new to Swift, but have experience with other programming languages, so I'm hoping to understand what I'm working with better.


Solution

  • There is no problem adding multiple child view controllers.

    Here's how it could look (in viewDidLoad() of the "parent" view controller):

        // instantiate 3 view controllers
        let firstVC = FirstVC()
        let secondVC = SecondVC()
        let thirdVC = ThirdVC()
    
        // for each view controller
        [firstVC, secondVC, thirdVC].forEach { vc in
            // add it as a child
            self.addChild(vc)
            // add its view
            view.addSubview(vc.view)
            // layout its view
            vc.view.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                // we'll constrain all 4 sides to the safe area
                //  with 20-points "padding" all around
                vc.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
                vc.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
                vc.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
                vc.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20.0),
            ])
            // start with all child controller views hidden
            vc.view.isHidden = true
            // finish the add child process
            vc.didMove(toParent: self)
        }
        
    

    View controllers maintain an array of "children" - so we can then "show" the first child's view like this:

        // show first child controller view
        self.children.first?.view.isHidden = false
    

    Here's a quick, runnable example...

    We create 3 view controllers, add them as child controllers, add their views (and set up constraints). Then we'll add a UISegmentedControl to switch between the child controllers' views.

    So, it will look like this:

    enter image description here

    enter image description here

    enter image description here

    class MultiChildViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let seg = UISegmentedControl(items: ["First", "Second", "Third"])
            seg.addTarget(self, action: #selector(segChanged(_:)), for: .valueChanged)
            seg.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(seg)
            
            // respect safe area
            let safeG = view.safeAreaLayoutGuide
            
            NSLayoutConstraint.activate([
                seg.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
                seg.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
                seg.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
            ])
    
            // instantiate 3 view controllers
            let firstVC = FirstVC()
            let secondVC = SecondVC()
            let thirdVC = ThirdVC()
    
            // for each view controller
            [firstVC, secondVC, thirdVC].forEach { vc in
                // add it as a child
                self.addChild(vc)
                // add its view
                view.addSubview(vc.view)
                // layout its view
                vc.view.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    // constrain top to segmented control bottom plus 20-points
                    vc.view.topAnchor.constraint(equalTo: seg.bottomAnchor, constant: 20.0),
                    // we'll constrain leading/trailing/bottom to the safe area
                    //  with 20-points "padding"
                    vc.view.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
                    vc.view.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
                    vc.view.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -20.0),
                ])
                // start with all child controller views hidden
                vc.view.isHidden = true
                // finish the add child process
                vc.didMove(toParent: self)
            }
            
            // set the segmented control's selected segment to Zero
            seg.selectedSegmentIndex = 0
            
            // show first child controller view
            self.children.first?.view.isHidden = false
    
        }
        
        @objc func segChanged(_ sender: UISegmentedControl) {
            let idx = sender.selectedSegmentIndex
            for (i, vc) in self.children.enumerated() {
                vc.view.isHidden = i != idx
            }
        }
    
    }
    
    class FirstVC: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemRed
            
            let v = UILabel()
            v.textAlignment = .center
            v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
            v.text = "First VC"
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            
            NSLayoutConstraint.activate([
                v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
                v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
                v.heightAnchor.constraint(equalToConstant: 100.0),
                v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
        }
    }
    
    class SecondVC: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemGreen
            
            let v = UILabel()
            v.textAlignment = .center
            v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
            v.text = "Second VC"
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            
            NSLayoutConstraint.activate([
                v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
                v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
                v.heightAnchor.constraint(equalToConstant: 100.0),
                v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
        }
    }
    
    class ThirdVC: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemBlue
            
            let v = UILabel()
            v.textAlignment = .center
            v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
            v.text = "Third VC"
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            
            NSLayoutConstraint.activate([
                v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
                v.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
                v.heightAnchor.constraint(equalToConstant: 100.0),
                v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
        }
    }