Custom View Transition without use of Storyboard segues (Swift)
Asked Answered
I

1

7

I have two views I would like to make a swipe style transition accross and I have done that when there is a segue to act on but the I don't have one here so am not sure how to apply my animation class. All I have in my class is:

let stb = UIStoryboard(name: "Walkthrough", bundle: nil)
    let walkthrough = stb.instantiateViewControllerWithIdentifier("walk") as! BWWalkthroughViewController

self.presentViewController(walkthrough, animated: true, completion: nil)

I want to apply apply the following custom segue:

class CustomSegue: UIStoryboardSegue {

  override func perform() {
    // Assign the source and destination views to local variables.
    var firstVCView = self.sourceViewController.view as UIView!
    var secondVCView = self.destinationViewController.view as UIView!

    // Get the screen width and height.
    let screenWidth = UIScreen.mainScreen().bounds.size.width
    let screenHeight = UIScreen.mainScreen().bounds.size.height

    // Specify the initial position of the destination view.
    secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)

    // Access the app's key window and insert the destination view above the current (source) one.
    let window = UIApplication.sharedApplication().keyWindow
    window?.insertSubview(secondVCView, aboveSubview: firstVCView)

    // Animate the transition.
    UIView.animateWithDuration(0.2, animations: { () -> Void in
      firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, 0.0)
      secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)

      }) { (Finished) -> Void in

      self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
          animated: false,
          completion:nil)
    }
  }
}

I cannot get it to work any pointers please?

Ignatia answered 27/7, 2015 at 0:2 Comment(1)
Note that you do not need to use UIScreen.mainScreen() and that it may actually create some confusion. Also note that if you slow down the animation to 4 seconds, it appears to reveal undesired artifacts.Ruebenrueda
R
5

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) 
    }
}

Below is the complete code example. It has been tested. Notice that I also fixed a few animation bugs from the original question, and added a fade-in effect to emphasize the animation.


CustomSegue Class

// 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)
    }
}

Host ViewController Class

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
        }
    }
}
Ruebenrueda answered 27/7, 2015 at 0:37 Comment(4)
Thank you I cannot see how to apply that to my code?Ignatia
Brilliant I want at home to apply it. I will have a go tonight after work, Amazing!Ignatia
I applied your code but I cannot animate width an height properties. Do you have any idea why that is?Ferruginous
tried this and transitionContext can not be nil. as transitionDuration(using: transitionContext!) implise not nil "transitionContext!".Coraliecoraline

© 2022 - 2024 — McMap. All rights reserved.