Exception in a transition in a custom container view controller

Hello,


I have a crash report of an app running on an iPhone 7 indicating that an exception has been thrown by the function

UIViewController.transitionFromViewController:toViewController:duration:options:animations:completion:
.


Unfortunately the crash report doesn't contain the exception name and reason.


Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0


Based on the doc found in the header file, I understand there are 2 possible reasons:

This method will fail with an NSInvalidArgumentException if the parent view controllers are not the same as the receiver, or if the receiver explicitly forwards its appearance and rotation callbacks to its children.


Knowing that the exception is thrown in UIViewController.m at line 11545, is it possible to know exactly what was the cause of the crash? I have put some safeguards for both cases but I would like to know the real issue just to understand why this problem occurs only rarely.


Thanks

Can you show how exactly you call this mehod ? And how the different parameters are defined.


And in which thread.

The function is "generic" in the sense that it implements the different calls required to do a child view controller transition. In practice the

options
and
animations
parameters are never used.
    private func transition(fromChild oldViewController: UIViewController, toChild newViewController: UIViewController, duration: Double = IPBORootViewController.animationDuration, options: UIViewAnimationOptions = [], animations: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
        self.addChildViewController(newViewController)
        oldViewController.willMove(toParentViewController: nil)
        self.transition(from: oldViewController, to: newViewController, duration: duration, options: options, animations: animations, completion: {
            _ in
            newViewController.didMove(toParentViewController: self)
            oldViewController.removeFromParentViewController()
            completion?()
        })
    }


The completion closure on the other hand runs an animation to remove a splash view:

        UIView.animate(withDuration: IPBORootViewController.animationDuration, animations: {
            splashView.alpha = 0.0
        }, completion: {
            _ in splashView.removeFromSuperview()
        })


Also it is always executed in a

runOnMain()
function that makes sure the thread is the main thread.
private func runOnMain(_ closure: @escaping () -> ()) {
        if Thread.isMainThread {
            closure()
        } else {
            DispatchQueue.main.async(execute: closure)
        }
    }

I may misunderstand your intent here :


        self.addChildViewController(newViewController)
        oldViewController.willMove(toParentViewController: nil)
        self.transition(from: oldViewController, to: newViewController, duration: duration, options: options, animations: animations, completion: {


But, I understood that

self.transition(from …

already add the newViewController to the views ;

So, why do you need to add yourself ?


What occurs if you comment out :

//        self.addChildViewController(newViewController)

My understanding is that the function transition adds the view to the view hierarchy but not the view controller to the view controller hierarchy. I based the code on an example published in the book "Programming iOS" Chapter "Container View Controllers" (very good by the way).

And what occurs if you comment out the addChild ?

Actually, if I remove this call, the transition method doesn't fail but nothing happens. Again, the exception appears very rarely. Most of the time the transition works as expected.

Did you try to put an assert on testing newViewController == nil ?

I read that transition(from: to:) does add the view controller:


transition(from:to:duration:options:animations:completion:)

Discussion

This method adds the second view controller'€™s view to the view hierarchy and then performs the animations defined in your animations block. After the animation completes, it removes the first view controller'€™s view from the view hierarchy.

This method is only intended to be called by an implementation of a custom container view controller. If you override this method, you must call

super
in your implementation.

My function is not overriding the UIViewController function (also its name is too close) so I suppose I don't need to call the super implementation. I would need to call the super implementation if the function was:


    override func transition(from fromViewController: UIViewController, to toViewController: UIViewController, duration: TimeInterval, options: UIViewAnimationOptions = [], animations: (() -> Void)?, completion: ((Bool) -> Void)? = nil) {
        super.transition(from: fromViewController, to: toViewController, duration: duration, options: options, animations: animations, completion: completion)
    }


My function is:

    private func transition(fromChild oldViewController: UIViewController, toChild newViewController: UIViewController, duration: Double = IPBORootViewController.animationDuration, options: UIViewAnimationOptions = [], animations: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
     // ...
Exception in a transition in a custom container view controller
 
 
Q