Search code examples
iosswiftuistoryboardsegue

Unbalanced calls to begin/end appearance transitions with custom segue


In implementing the following cross-dissolve custom segue on a button press (learned at https://www.youtube.com/watch?v=xq9ZVsLNcWw):

override func perform() {

    var src:UIViewController = self.sourceViewController as! UIViewController
    var dstn:UIViewController = self.destinationViewController as! UIViewController

    src.view.addSubview(dstn.view)

   dstn.view.alpha = 0

    UIView.animateWithDuration(0.75 , delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: { () -> Void in


        dstn.view.alpha = 1


    }) { (finished) -> Void in

        dstn.view.removeFromSuperview()
        src.presentViewController(dstn, animated: false, completion: nil)

    }

}

I am getting the "Unbalanced calls to begin/end appearance transitions" warning/error.

I have thoroughly searched many stackoverflow questions:

Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x197870>

  • my response: I am using the segue as a button press not within a TabBar Controller so this answer does not apply

Keep getting "Unbalanced calls to begin/end appearance transitions for <ViewController>" error

  • my response: I tried two methods: 1) All segues done programatically with self.performseguewithidentifier 2) All segues done via interface builder, by click dragging the buttons and selecting my custom transition from the attributes pane of the selected segue. Both still yielded the aforementioned error.

"Unbalanced calls to begin/end appearance transitions" warning when push a view in a modal way in XCode 4 with Storyboard

  • my response: Same as above, double checked all segues were only performed once, either programmatically or via interface builder, not both.

"Unbalanced calls to begin/end appearance transitions for DetailViewController" when pushing more than one detail view controller

  • my response: I am not using a table view controller nor a navigation controller in my segues, so this answer does not apply.

Unbalanced calls to begin/end appearance transitions for UITabBarController

  • my response: I tried making all segues perform within a dispatch as shown below

    let delay = 0.01 * Double(NSEC_PER_SEC)
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
    dispatch_after(time, dispatch_get_main_queue()) {
    
       self.performSegueWithIdentifier("toNonFBLogin", sender: nil)
    
     }
    

however still to no avail, the warning/error still pops up.

I'm aware what the error means, that it's attempting to present a new viewcontroller before the previous one loads, but not sure how to tackle it.

The only lead I have is that the previous (source) viewcontroller pops up real quickly before the final viewcontroller loads, as seen at 0:04 at

https://youtu.be/i1D5fbcjNjY

The glitch only actually appears I would guess around 5% of the time, however.

Any ideas?


Solution

  • Looks like this behaviour is the result of some - as of yet - undocumented change in recent iOS releases. Getting frustrated with your exact issue and the lack of satisfying answers I've come up with this:

    // create a UIImageView containing a UIImage of `view`'s contents
    func createMockView(view: UIView) -> UIImageView {
        UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.mainScreen().scale)
    
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
    
        UIGraphicsEndImageContext()
        return UIImageView(image: image)
    }
    
    override func perform() {
    
       let src:UIViewController = self.sourceViewController as! UIViewController
       let dstn:UIViewController = self.destinationViewController as! UIViewController
       let mock = createMockView(dstn.view)
    
       src.view.addSubview(mock)
       mock.alpha = 0
    
       UIView.animateWithDuration(0.75, delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve, 
         animations: { () -> Void in
            mock.alpha = 1
         },
         completion: { (finished) -> Void in
            src.presentViewController(dstn, animated: false, completion: { mock.removeFromSuperView()})
       })
    }
    

    createMockView() will create a UIImageView containing a snapshot of the destination VCs contents and use that to do the transition animation. Once the transition is finished the real destination VC is presented without animation causing a seamless transition between both. Finally once the presentation is done the mock view is removed.