Animated Transition adopting two Protocols

This article describes how, by adopting multiple distinct protocols simultaneously, you can design and reuse animated `UIView` transitions with and without `Storyboard`.

The Architect Approach: Zero coding.

aStoryboard

While the first answer should be “use Storyboard Segues”, you can solve custom transitions this way:

Generic Approach

1. Modify your `CustomSegue` to adopt both `UIStoryboardSegue` and `UIViewControllerAnimatedTransitioning` protocols.
2. Refactor `CustomSegue` so that the animation can be used by both protocols.
3. Setup a `delegate` to the navigation controller, which can be itself, to supply the custom transitions to `push` & `pop`
4. Let `animationControllerForOperation` create and return an instance of `CustomSegue`, with an identifier of your choice.

Overview

// Adopt both protocols
class CustomSegue: UIStoryboardSegue, UIViewControllerAnimatedTransitioning {

    func animate(firstVCView:UIView,
        secondVCView:UIView,
        containerView:UIView,
        transitionContext: UIViewControllerContextTransitioning?) {
            // factored transition code goes here
                }) { (Finished) -> Void in
                    if let context = transitionContext {
                        // UIViewControllerAnimatedTransitioning
                    } else {
                        // UIStoryboardSegue
                    }
            }
    }
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        // return timing
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        // Perform animate using transitionContext information
        // (UIViewControllerAnimatedTransitioning)
    }
    
    override func perform() {
        // Perform animate using segue (self) variables
        // (UIStoryboardSegue) 
    }
}

 

Complete code example.

Note: added a fade-in effect to emphasize the animation.

`CustomSegue`

// Animation for both a Segue and a Transition
class CustomSegue: UIStoryboardSegue, UIViewControllerAnimatedTransitioning {

    func animate(firstVCView:UIView,
        secondVCView:UIView,
        containerView:UIView,
        transitionContext: UIViewControllerContextTransitioning?) {
            
            // Get the screen width and height.
            let offset = secondVCView.bounds.width
            
            // Specify the initial position of the destination view.
            secondVCView.frame = CGRectOffset(secondVCView.frame, offset, 0.0)
            
            firstVCView.superview!.addSubview(secondVCView)
            secondVCView.alpha = 0;
            
            // Animate the transition.
            UIView.animateWithDuration(self.transitionDuration(transitionContext!),
                animations: { () -> Void in
                    firstVCView.frame = CGRectOffset(firstVCView.frame, -offset, 0.0)
                    secondVCView.frame = CGRectOffset(secondVCView.frame, -offset, 0.0)
                    secondVCView.alpha = 1; // emphasis
                    
                }) { (Finished) -> Void in
                    if let context = transitionContext {
                        context.completeTransition(!context.transitionWasCancelled())
                    } else {
                        self.sourceViewController.presentViewController(
                            self.destinationViewController as! UIViewController,
                            animated: false,
                            completion:nil)
                    }
            }
    }
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 4 // four seconds
    }

    // Perform Transition (UIViewControllerAnimatedTransitioning)
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        self.animate(transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view,
            secondVCView: transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view,
            containerView: transitionContext.containerView(),
            transitionContext: transitionContext)
    }
    
    // Perform Segue (UIStoryboardSegue)
    override func perform() {
        self.animate(self.sourceViewController.view!!,
            secondVCView: self.destinationViewController.view!!,
            containerView: self.sourceViewController.view!!.superview!,
            transitionContext:nil)
    }
}

`ViewController`

class ViewController: UIViewController, UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationController?.delegate = self
    }
    
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        switch operation {
        case .Push:
             return CustomSegue(identifier: "Abc", source: fromVC, destination: toVC)
            
        default:
            return nil
        }
    }
}

 

Read more on StackOverflow question 31643228. (https://stackoverflow.com/a/31643461/218152)

Xavier Schott

0010 0000 years of algorithm crafting, software architecture, and bringing visionary mobile apps to market.