In this episode we'll create a custom view controller animation that mimics the Magic Move behavior from keynote, taking one object and animating into its place on the next slide (or view controller).
Episode Links Source Code objc.io - View Controller Transitions a very helpful article by the Chris Eidhof. This covers the navigation controller delegate approach I mention in the episode. UIViewControllerAnimatedTransitioning Protocol Reference UIViewControllerContextTransitioning Protocol Reference Creating an Animator class We start by creating an Animator class that will hold the logic for animating between two view controllers. class Animator : NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 0.6 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // ... } } Our Animator will handle the push and the pop, which will have slightly different semantics. For this we'll need a boolean property to determine if we're presenting or not: class Animator : NSObject, UIViewControllerAnimatedTransitioning { var presenting = false // ... func animateTransition(transitionContext: UIViewControllerContextTransitioning) { if presenting { animatePush(transitionContext) } else { animatePop(transitionContext) } } func animatePush(transitionContext: UIViewControllerContextTransitioning) { } func animatePop(transitionContext: UIViewControllerContextTransitioning) { } } Animating the Push Animating the push will entail: Getting the frame of the selected cell's image view Creating a snapshot view that looks just like the view to be moved (the image view) Positioning the snapshot view just above the source view (while hiding the source & destination view) Animating the snapshot to the final position Animating the rest of the view controller into position Hiding the snapshot and re-showing both image views func animatePush(transitionContext: UIViewControllerContextTransitioning) { let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! as! ViewController let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! as! DetailViewController let container = transitionContext.containerView() // we're going to need the frames of the target view, so // make sure it has laid out properly. toVC.view.setNeedsLayout() toVC.view.layoutIfNeeded() let fromImageView = getCellImageView(fromVC) let toImageView = toVC.imageView let snapshot = fromImageView.snapshotViewAfterScreenUpdates(false) fromImageView.hidden = true toImageView.hidden = true let backdrop = UIView(frame: toVC.view.frame) backdrop.backgroundColor = toVC.view.backgroundColor container.addSubview(backdrop) backdrop.alpha = 0 toVC.view.backgroundColor = UIColor.clearColor() toVC.view.alpha = 0 let finalFrame = transitionContext.finalFrameForViewController(toVC) var frame = finalFrame frame.origin.y += frame.size.height toVC.view.frame = frame container.addSubview(toVC.view) snapshot.frame = container.convertRect(fromImageView.frame, fromView: fromImageView) container.addSubview(snapshot) UIView.animateWithDuration(transitionDuration(transitionContext) , animations: { backdrop.alpha = 1 toVC.view.alpha = 1 toVC.view.frame = finalFrame snapshot.frame = container.convertRect(toImageView.frame, fromView: toImageView) }, completion: { (finished) in toVC.view.backgroundColor = backdrop.backgroundColor backdrop.removeFromSuperview() fromImageView.hidden = false toImageView.hidden = false snapshot.removeFromSuperview() transitionContext.completeTransition(finished) }) } Animating the Pop Animating the pop is very similar, but everything is done in reverse. There's probably an opportunity to factor out some duplicate logic here. func animatePop(transitionContext: UIViewControllerContextTransitioning) { let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! as! DetailViewController let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! as! ViewController let container = transitionContext.containerView() let fromImageView = fromVC.imageView let toImageView = getCellImageView(toVC) let snapshot = fromImageView.snapshotViewAfterScreenUpdates(false) fromImageView.hidden = true toImageView.hidden = true let backdrop = UIView(frame: fromVC.view.frame) backdrop.backgroundColor = fromVC.view.backgroundColor container.insertSubview(backdrop, belowSubview: fromVC.view) backdrop.alpha = 1 fromVC.view.backgroundColor = UIColor.clearColor() let finalFrame = transitionContext.finalFrameForViewController(toVC) toVC.view.frame = finalFrame var frame = finalFrame frame.origin.y += frame.size.height container.insertSubview(toVC.view, belowSubview: backdrop) snapshot.frame = container.convertRect(fromImageView.frame, fromView: fromImageView) container.addSubview(snapshot) UIView.animateWithDuration(transitionDuration(transitionContext) , animations: { backdrop.alpha = 0 fromVC.view.frame = frame snapshot.frame = container.convertRect(toImageView.frame, fromView: toImageView) }, completion: { (finished) in fromVC.view.backgroundColor = backdrop.backgroundColor backdrop.removeFromSuperview() fromImageView.hidden = false toImageView.hidden = false snapshot.removeFromSuperview() transitionContext.completeTransition(finished) }) }