6

I have a custom UIStoryboardSegue that works as desired in iOS12.*.

One of the destination view controller is a UITabbarController: for each tab, I have a controller embedded in a navigation controller.

Unfortunately, for iOS13.*, this does not work well: the view controller lifecycle is broken, and no call the viewXXXAppear() nor the willTransition() methods are no longer issued.

It looks like makeKeyAndVisible() has no effect?!

See at the bottom how the screen UI is puzzled below without viewWillAppear() being called.

An horrible temporary workaround

I had to pull my hairs but, I have found a fix which I make public (I had to add a navigation controller on the fly).

This messes the vc hierarchy: do you have a better solution?

public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
    override public func perform() {

        guard let window = UIApplication.shared.delegate?.window as? UIWindow,
            let sourceView = source.view,
            let destinationView = destination.view else {
            super.perform()
            return
        }

        let screenWidth  = UIScreen.main.bounds.size.width
        let screenHeight = UIScreen.main.bounds.size.height

        destinationView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

        window.insertSubview(destinationView, aboveSubview: sourceView)


        // **My fix**
        if #available(iOS 13,*) {
            // I introduced an invisible navigation controller starting in iOS13 otherwise, my controller attached to the tabbar thru a navigation, dont work correctly, no viewXAppearis called.
            let navigationController = UINavigationController.init(rootViewController: self.destination)
            navigationController.isNavigationBarHidden = true
            window.rootViewController = navigationController
        }
        else {
            window.rootViewController = self.destination
        }

        window.makeKeyAndVisible()
    }
}

UI destroyed

Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • Did you ever find a solution? I have same exact problem, and can't use that workaround – mesh Jul 28 '20 at 17:46

2 Answers2

1

I found a solution thanks to Unbalanced calls to begin/end appearance transitions with custom segue What happens here is that the creation and attaching of the destination view controller happens twice, and the first one happens too soon. So what you need to do is:

public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
    override public func perform() {

        guard let window = UIApplication.shared.delegate?.window as? UIWindow,
            let sourceView = source.view,
            let destinationView = destination.view else {
            super.perform()
            return
        }

        let screenWidth  = UIScreen.main.bounds.size.width
        let screenHeight = UIScreen.main.bounds.size.height

        let mock = createMockView(view: desination.view)
        window.insertSubview(mock, aboveSubview: sourceView)

        //DO SOME ANIMATION HERE< MIGHT NEED TO DO mock.alpha = 0

        //after the animation is done:
        window.rootViewController = self.destination
        mock.removeFromSuperview()
    }

    func createMockView(view: UIView) -> UIImageView {
        UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.main.scale)

        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()
        return UIImageView(image: image)    
    }   
}
mesh
  • 113
  • 5
0

I had a similar problem on iOS 13 when performing a custom storyboard segue that replaces the rootViewController. The original code looked like this:

 @interface CustomSegue : UIStoryboardSegue 

 @end 

 @implementation CustomSegue 

 - (void)perform {
     AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];

     UIViewController *destination = (UIViewController *) self.destinationViewController;
     [destination.view removeFromSuperview];
     [appDelegate.window addSubview:destination.view];
     appDelegate.window.rootViewController = destination;
 } 

 @end

Removing the line [appDelegate.window addSubview:destination]; fixed the problem to me. Apparanently, it was unnecessary to add the new VC's view as a subview to the window. It did the job correctly even after removing that line, and it also fixed the error message "unbalanced calls to begin/end appearance transitions".

zvonicek
  • 1,503
  • 16
  • 26