0

I've created a custom segue which is a reversed version of a vertical segue, here is my perform function:

var sourceViewController = self.sourceViewController as UIViewController!
var destinationViewController = self.destinationViewController as UIViewController!

sourceViewController.view.addSubview(destinationViewController.view)
destinationViewController.view.frame = sourceViewController.view.frame
destinationViewController.view.transform = CGAffineTransformMakeTranslation(0, -sourceViewController.view.frame.size.height)
destinationViewController.view.alpha = 1.0

UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
    destinationViewController.view.transform = CGAffineTransformMakeTranslation(0, 0)
    }) { (finished: Bool) -> Void in
            destinationViewController.view.removeFromSuperview()
            sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)
    }

When I perform it in my app it works and the animation is exactly what I want but I have this warning in the console:

Unbalanced calls to begin/end appearance transitions for <Custom_Segues.ViewController: 0x7a3f9950>.

I read many posts concerning this problem on Stack Overflow but I didn't find one linked to my situation, does someone know what is the problem? I tried many things on my code and I know the problem is in the two last lines but I don't know what to change.

EDIT/ANSWER:

After reading the answers I found a solution: changing the view then applying the old VC on the new one and do the animation. The code is safe and there is no flash of the old VC at the end of the animation.

var sourceViewController = self.sourceViewController as UIViewController!
var destinationViewController = self.destinationViewController as UIViewController!
var duplicatedSourceView: UIView = sourceViewController.view.snapshotViewAfterScreenUpdates(false) // Screenshot of the old view.

destinationViewController.view.addSubview(duplicatedSourceView) // We add a screenshot of the old view (Bottom) above the new one (Top), it looks like nothing changed.

sourceViewController.presentViewController(destinationViewController, animated: false, completion: {
    destinationViewController.view.addSubview(duplicatedSourceView) // We add the old view (Bottom) above the new one (Top), it looks like nothing changed.

    UIView.animateWithDuration(0.33, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
        duplicatedSourceView.transform = CGAffineTransformMakeTranslation(0, sourceViewController.view.frame.size.height) // We slide the old view at the bottom of the screen
            }) { (finished: Bool) -> Void in
            duplicatedSourceView.removeFromSuperview()
        }
    })
}
Armand Grillet
  • 3,229
  • 5
  • 30
  • 60
  • Hm, can you put an NSLog or println in the completion block next to your presentViewController call to see how many times it's getting run? It may get run more than once... – Daniel Brim Aug 15 '14 at 17:16
  • The block at the end of animateWithDuration() runs only once when you click on it (I followed your instructions to check). – Armand Grillet Aug 15 '14 at 17:19

2 Answers2

1

This looks runloop timing related.

destinationViewController.view.removeFromSuperview()
sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)

These two lines should not execute within the same runloop.

destinationViewController.view.removeFromSuperview()

// Force presentViewController() into a different runloop.
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
    sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)
}
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • Thank you for your answer! I have another question linked to this answer: how could I remove the "flash" of the old view controller? You can see it during the interval between removeFromSuperview() and presentViewController() and it kinda ruins the effect of the segue. – Armand Grillet Aug 15 '14 at 19:23
  • 1
    You can make a UIImage from your UIView and show that while you present, see http://stackoverflow.com/questions/4334233/how-to-capture-uiview-to-uiimage-without-loss-of-quality-on-retina-display. I think you may want to consider taking a different approach, see http://stackoverflow.com/questions/19931710/how-to-custom-modal-view-controller-presenting-animation – Jeffery Thomas Aug 15 '14 at 19:34
1

I added this as a comment, but I think it good enough to make a second solution. After looking at How to custom Modal View Controller presenting animation?

The solution converted to swift:

var transition = CATransition()
transition.duration = 1
transition.type = kCATransitionFade
transition.subtype = kCATransitionFromBottom

sourceViewController.view.window?.layer.addAnimation(transition, forKey: kCATransition)
sourceViewController.presentViewController(destinationViewController, animated:false, completion:nil)

You can adjust this to match your needs.

Community
  • 1
  • 1
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117