Custom Flip Segue in Swift

Here is my code for my custom Segue

class FlipFromRightSegue: UIStoryboardSegue { override func perform() { let source:UIViewController = self.sourceViewController as UIViewController let destination:UIViewController = self.destinationViewController as UIViewController UIView.transitionWithView(source.view, duration: 1.0, options: .CurveEaseInOut | .TransitionFlipFromRight, animations: { () -> Void in source.view.addSubview(destination.view) }) { (finished) -> Void in destination.view.removeFromSuperview() source.presentViewController(destination, animated: false, completion: nil) } } } 

I thought this worked, but actually the view only changes when segue is already executed. What should I do to make the view change when "Flip" is in the middle?

Thanks in advance.

+5
source share
1 answer

As with iOS 7, we generally don’t animate transitions using custom segue. We either used the standard modal presentation by specifying modalTransitionStyle (i.e. a fixed list of several animations that we can choose for our modal transitions), or you will implement custom animation transitions. Both of these are described below:

  • If you just imagine a different kind of view controller, a simple solution to change the animation to flip is to set modalTransitionStyle in the destination view controller. You can do this completely in the Builder interface in the segue properties.

    If you want to do this programmatically, in the destination controller you can do the following in Swift 3:

     override func viewDidLoad() { super.viewDidLoad() modalTransitionStyle = .flipHorizontal // use `.FlipHorizontal` in Swift 2 } 

    Then, when you call show / showViewController or present / presentViewController , and your presentation will be presentViewController horizontally. And when you reject the view controller, the animation is automatically canceled for you.

  • If you need more control, in iOS 7 and later, you should use custom animation transitions in which you must specify modalPresentationStyle of .custom . For example, in Swift 3:

     class SecondViewController: UIViewController { let customTransitionDelegate = TransitioningDelegate() required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) modalPresentationStyle = .custom // use `.Custom` in Swift 2 transitioningDelegate = customTransitionDelegate } ... } 

    Specifies the UIViewControllerTransitioningDelegate that will instantiate the animation controller. For example, in Swift 3:

     class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimationController(transitionType: .presenting) } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimationController(transitionType: .dismissing) } } 

    And the animation controller would just do .transitionFlipFromRight - this is a presentation or .transitionFlipFromLeft if the deviation is in Swift 3:

     class AnimationController: NSObject, UIViewControllerAnimatedTransitioning { enum TransitionType { case presenting case dismissing } var animationTransitionOptions: UIViewAnimationOptions init(transitionType: TransitionType) { switch transitionType { case .presenting: animationTransitionOptions = .transitionFlipFromRight case .dismissing: animationTransitionOptions = .transitionFlipFromLeft } super.init() } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { //let inView = transitionContext.containerView let toView = transitionContext.viewController(forKey: .to)?.view let fromView = transitionContext.viewController(forKey: .from)?.view UIView.transition(from: fromView!, to: toView!, duration: transitionDuration(using: transitionContext), options: animationTransitionOptions.union(.curveEaseInOut)) { finished in transitionContext.completeTransition(true) } } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 } } 

    For more information about custom transitions introduced in iOS 7, see the WWDC 2013 video Custom transitions using view controllers .

    / li>
  • If you need to admit that the above AnimationController is actually over simplification, because we use transform(from:to:...) . This results in an animation that is not canceled (in case you are using an interactive transition). It also removes the "from" the view itself, and with iOS 8 - this is really the work of the view controller.

    So, you really want to do flip animation using the UIView.animate API. I apologize because the following includes the use of the non-intuitive CATransform3D in keyframe animations, but this leads to flip animations that can then be canceled by interactive transitions.

    So, in Swift 3:

     class AnimationController: NSObject, UIViewControllerAnimatedTransitioning { enum TransitionType { case presenting case dismissing } let transitionType: TransitionType init(transitionType: TransitionType) { self.transitionType = transitionType super.init() } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let inView = transitionContext.containerView let toView = transitionContext.view(forKey: .to)! let fromView = transitionContext.view(forKey: .from)! var frame = inView.bounds func flipTransform(angle: CGFloat, offset: CGFloat = 0) -> CATransform3D { var transform = CATransform3DMakeTranslation(offset, 0, 0) transform.m34 = -1.0 / 1600 transform = CATransform3DRotate(transform, angle, 0, 1, 0) return transform } toView.frame = inView.bounds toView.alpha = 0 let transformFromStart: CATransform3D let transformFromEnd: CATransform3D let transformFromMiddle: CATransform3D let transformToStart: CATransform3D let transformToMiddle: CATransform3D let transformToEnd: CATransform3D switch transitionType { case .presenting: transformFromStart = flipTransform(angle: 0, offset: inView.bounds.size.width / 2) transformFromEnd = flipTransform(angle: -.pi, offset: inView.bounds.size.width / 2) transformFromMiddle = flipTransform(angle: -.pi / 2) transformToStart = flipTransform(angle: .pi, offset: -inView.bounds.size.width / 2) transformToMiddle = flipTransform(angle: .pi / 2) transformToEnd = flipTransform(angle: 0, offset: -inView.bounds.size.width / 2) toView.layer.anchorPoint = CGPoint(x: 0, y: 0.5) fromView.layer.anchorPoint = CGPoint(x: 1, y: 0.5) case .dismissing: transformFromStart = flipTransform(angle: 0, offset: -inView.bounds.size.width / 2) transformFromEnd = flipTransform(angle: .pi, offset: -inView.bounds.size.width / 2) transformFromMiddle = flipTransform(angle: .pi / 2) transformToStart = flipTransform(angle: -.pi, offset: inView.bounds.size.width / 2) transformToMiddle = flipTransform(angle: -.pi / 2) transformToEnd = flipTransform(angle: 0, offset: inView.bounds.size.width / 2) toView.layer.anchorPoint = CGPoint(x: 1, y: 0.5) fromView.layer.anchorPoint = CGPoint(x: 0, y: 0.5) } toView.layer.transform = transformToStart fromView.layer.transform = transformFromStart inView.addSubview(toView) UIView.animateKeyframes(withDuration: self.transitionDuration(using: transitionContext), delay: 0, options: [], animations: { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.0) { toView.alpha = 0 fromView.alpha = 1 } UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { toView.layer.transform = transformToMiddle fromView.layer.transform = transformFromMiddle } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.0) { toView.alpha = 1 fromView.alpha = 0 } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) { toView.layer.transform = transformToEnd fromView.layer.transform = transformFromEnd } }, completion: { finished in toView.layer.transform = CATransform3DIdentity fromView.layer.transform = CATransform3DIdentity toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) fromView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 } } 
  • FYI, iOS 8 extends custom transition model with view controllers. For more information, see WWDC 2014 Video View inside presentation controllers .

    In any case, if the β€œfrom” view is no longer displayed at the end of the transition, you should instruct your presentation controller to remove it from the presentation hierarchy, for example:

     class PresentationController: UIPresentationController { override var shouldRemovePresentersView: Bool { return true } } 

    And, obviously, you should tell your TransitioningDelegate this presentation controller:

     class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { ... func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return PresentationController(presentedViewController: presented, presenting: presenting) } } 

This answer has been updated for Swift 3. Please refer to the previous version of this answer if you want to see the Swift 2 implementation.

+20
source

Source: https://habr.com/ru/post/1203539/


All Articles