一尘不染

Swift中的自定义翻转Segue

swift

这是我的自定义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)
    }
}
}

我以为这可行,但实际上只有在执行segue后,视图才会改变。我应该怎么做才能使视图在中间出现“翻转”时发生变化?

提前致谢。


阅读 233

收藏
2020-07-07

共1个答案

一尘不染

从iOS
7开始,我们通常不使用自定义序列为过渡设置动画。我们要么使用标准的模态演示,指定一个modalTransitionStyle(即,可以为模态过渡选择的一些动画的固定列表),要么实现自定义动画过渡。这两个描述如下:

  1. 如果仅显示另一个视图控制器的视图,则将动画更改为翻转的简单解决方案是modalTransitionStyle在目标视图控制器中设置。您可以完全在segue属性下的Interface Builder中完成此操作。

如果要以编程方式执行此操作,则可以在目标控制器中的Swift 3中执行以下操作:

    override func viewDidLoad() {
    super.viewDidLoad()

    modalTransitionStyle = .flipHorizontal   // use `.FlipHorizontal` in Swift 2
}

然后,当您调用show/
showViewControllerpresent/时presentViewController,演示将通过水平翻转进行。而且,当您关闭视图控制器时,动画会自动为您反转。

  1. 如果需要更多控制,请在iOS 7及更高版本中使用自定义动画过渡,在其中指定modalPresentationStyle.custom。例如,在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
    }
    
    ...
    

    }

这指定了UIViewControllerTransitioningDelegate将实例化动画控制器的。例如,在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)
    }
}

动画控制器只是做.transitionFlipFromRight一个演示,或者.transitionFlipFromLeft在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
    }
}

有关iOS 7中引入的自定义过渡的更多信息,请参阅WWDC
2013视频使用视图控制器的自定义过渡

  1. 如果应该承认上述AnimationController内容实际上是过分简化,因为我们正在使用transform(from:to:...)。这样会导致动画无法取消(以防您使用交互式过渡)。它还删除了“ from”视图本身,从iOS 8开始,这实际上就是表示控制器的工作。

因此,您真的想使用UIView.animateAPI
进行翻转动画。我很抱歉,因为以下内容涉及CATransform3D在关键帧动画中使用一些不直观的内容,但是会导致翻转动画,然后可以对其进行可取消的交互式过渡。

因此,在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
    }
}
  1. 仅供参考,iOS 8通过演示控制器扩展了自定义过渡模型。有关更多信息,请参阅WWDC 2014视频A Look Inside Presentation Controllers

无论如何,如果在过渡结束时不再可见“ from”视图,则应指示演示控制器将其从视图层次结构中删除,例如:

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

而且,显然,您必须TransitioningDelegate将此演示控制器通知您:

    class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {

    ...

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

此答案已针对Swift 3进行了更新。

2020-07-07