Home > OS >  Can you call addChild multiple times on UIViewController? What's best practice?
Can you call addChild multiple times on UIViewController? What's best practice?

Time:07-09

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.

CodePudding user response:

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),
        ])
    }
}
  • Related