0

I am creating two child view controllers childA and childB to be displayed and managed by a view controller parent. I'm hoping to master how nested child controllers work, but right now end up with a crash.

I created a generic extension to UIViewController to help me add a child controller:

@nonobjc public extension UIViewController {

    func addChildController(_ child: UIViewController, frame: CGRect? = nil) {
        addChild(child)
        if let frame = frame {
            child.view.frame = frame
        }
        view.addSubview(child.view)
        child.didMove(toParent: self)
    }
}

I start with a parent controller that adds a child controller, which is revealed when I hit the "preview" button:

class ParentViewController: UIViewController {

    lazy var childA = ChildAViewController(nibName: "ChildAViewController", bundle: nil)

    override func viewDidLoad() {
        super.viewDidLoad()
        addChildController(childA, frame: view.bounds)
    }

    @IBAction func preview(_ sender: Any) {
        childA.view.isHidden = false
    }
}

This childA controller in turn adds its own child controller childB. There are buttons on each of these controllers' views to flip back and forth between them.

class ChildAViewController: UIViewController {

    lazy var childB = ChildBViewController(nibName: "ChildBViewController", bundle: nil)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.isHidden = true

        addChildController(childB, frame: view.bounds)
        childB.childA = self
    }

    @IBAction func showChildBView(_ sender: Any) {
        childB.view.isHidden = false
        let transitionOptions: UIView.AnimationOptions = [.transitionFlipFromRight]
        UIView.transition(from: view, to: childB.view, duration: 1, options: transitionOptions) { success in
            self.view.isHidden = true
        }
    }
}


class ChildBViewController: UIViewController {

    var childA: ChildAViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.isHidden = true
    }

    @IBAction func showChildAView(_ sender: Any) {
        childA!.view.isHidden = false
        let transitionOptions: UIView.AnimationOptions = [.transitionFlipFromRight]
        UIView.transition(from: view, to: childA!.view, duration: 1, options: transitionOptions) { success in
            self.view.isHidden = true
        }
    }
}

What's strange is that I can flip back and forth, displaying A then B then A, but then when it's about to display B again, the app suddenly it crashes with this error:

2019-11-20 16:09:19.984575-0800 MyApp[95630:6863114] *** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<MyApp.ChildBController: 0x7fb05a03b200> should have parent view controller:<MyApp.ParentController: 0x7fb05945bf10> but actual parent is:<MyApp.ChildAController: 0x7fb05a074e00>'

What's going on here? I don't have this issue if I create both childA and childB as children of my parent, not a child and a 'grandchild'. But I want the ability to nest child view controllers as stated in the documentation, and in fact actually want to nest more deeply.

Dylan
  • 2,315
  • 2
  • 20
  • 33
  • You can try adding `.showHideTransitionViews` to your animation options; Without this, the views are moved in and out of the destination superviews and your view is ending up owned by the wrong superview. – Paulw11 Nov 21 '19 at 01:50
  • First, I would not play with extensions until you have everything working. Once you've figured it all out, then create an extension. Just as practice, you shouldn't extend things that aren't finished and in this case, I think it just complicates things. Second, after you add a child `self.addChild(childVC)` you must call `childVC.didMove(toParent: self)`. Third, in your example, the way you show and hide the two child view controllers, that behavior should be done by a single parent. I know you want nesting behavior, but this use case doesn't fit IMO. – trndjc Nov 21 '19 at 01:52

0 Answers0