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