Swift 动画 —— controller转场动画

Controller的转场动画主要是通过UIViewControllerAnimatedTransitioning 和 UIViewControllerTransitioningDelegate 来完成。
实现效果:
请添加图片描述
首先,创建一个class PresentViewControllerAnimation,继承自NSObject,遵守UIViewControllerAnimatedTransitioning。然后创建一个初始化方法,声明一个originFrame来保存初始化的frame。这里遵守UIViewControllerAnimatedTransitioning之后需要实现两个方法,一个是transitionDuration,一个是animateTransition。transitionDuration是代表动画时长,那么animateTransition里面就是动画的实现。这里

  • login button 沿y轴顺时针旋转90度。
  • 以login button 的大小在该位置snapshot,但初始位置在y轴上逆时针90度,这样让 login button 旋转看起来连续。
  • 然后沿着y轴顺时针旋转90度。
  • 放大snapshot填满屏幕。

动画完了之后,将toview 显示出来,将snapshot移除,将 login button 位置还原。记得还需要调用transitionContext.completeTransition方法,否则页面会卡着不动。

class PresentViewControllerAnimation: NSObject,UIViewControllerAnimatedTransitioning {
    private let originFrame: CGRect

    init(originFrame: CGRect) {
        self.originFrame = originFrame
    }
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 2.0
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from) as? LoginViewController,
            let toVC = transitionContext.viewController(forKey: .to) as? GiftViewController,
            let snapshot = toVC.view.snapshotView(afterScreenUpdates: true)
            else {
                return
        }

        let containerView = transitionContext.containerView
        let finalFrame = transitionContext.finalFrame(for: toVC)

        snapshot.frame = originFrame
        snapshot.layer.cornerRadius = originFrame.width / 2
        snapshot.layer.masksToBounds = true
        snapshot.layer.borderColor = UIColor.brown.cgColor
        snapshot.layer.borderWidth = 1

        containerView.addSubview(toVC.view)
        containerView.addSubview(snapshot)
        toVC.view.isHidden = true
        
        snapshot.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi / 2), 0.0, 1.0, 0.0)
        let duration = transitionDuration(using: transitionContext)

        UIView.animateKeyframes(
            withDuration: duration,
            delay: 0,
            options: .calculationModeCubic,
            animations: {
                UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/4) {
                    fromVC.loginButton.layer.transform = CATransform3DMakeRotation(CGFloat(-Double.pi / 2), 0.0, 1.0, 0.0)
                }

                UIView.addKeyframe(withRelativeStartTime: 1/4, relativeDuration: 1/4) {
                    snapshot.layer.transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0)
                }

                UIView.addKeyframe(withRelativeStartTime: 2/4, relativeDuration: 1/2) {
                    snapshot.frame = finalFrame
                }
        },
            completion: { _ in
                toVC.view.isHidden = false
                snapshot.removeFromSuperview()
                fromVC.loginButton.layer.transform = CATransform3DIdentity
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        })
    }
    
}

回到LoginViewController,将GiftViewController的transitioningDelegate 设为self。

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destinationViewController = segue.destination as? GiftViewController {
            destinationViewController.transitioningDelegate = self
            destinationViewController.modalPresentationStyle = .overFullScreen
        }
    }

然后在animationControllerforPresented回调方法中将刚才创建的转场动画设置好,这样入场动画就弄好了,接下来弄dismiss的动画。

extension LoginViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentViewControllerAnimation(originFrame: loginButton.frame)
    }
}

同样的,创建一个class DismissViewControllerAnimation,继承自NSObject,遵守UIViewControllerAnimatedTransitioning。然后创建一个初始化方法,声明一个destinationFrame来保存初始化的frame,声明一个interactiveControl来保存SwipeInteractiveTransition,这里也做了动画的处理等。

class DismissViewControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {

    private let destinationFrame: CGRect
    let interactiveControl: SwipeInteractiveTransition?

    init(destinationFrame: CGRect, interactionController: SwipeInteractiveTransition? = nil) {
        self.destinationFrame = destinationFrame
        self.interactiveControl = interactionController
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1.0
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from),
            let toVC = transitionContext.viewController(forKey: .to) as? LoginViewController,
            let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false)
            else {
                return
        }

        let containerView = transitionContext.containerView
        fromVC.view.isHidden = true
        containerView.addSubview(snapshot)
        snapshot.layer.cornerRadius = destinationFrame.width / 2
        snapshot.layer.masksToBounds = true
        snapshot.layer.borderColor = UIColor.brown.cgColor
        snapshot.layer.borderWidth = 1

        let duration = transitionDuration(using: transitionContext)

        UIView.animateKeyframes(
            withDuration: duration,
            delay: 0,
            options: .calculationModeLinear,
            animations: {
                UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/2) {
                    snapshot.frame = self.destinationFrame
                }

                UIView.addKeyframe(withRelativeStartTime: 1/2, relativeDuration: 1/4) {
                    snapshot.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi/2), 0.0, 1.0, 0.0)
                    toVC.loginButton.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi/2), 0.0, 1.0, 0.0)
                }

                UIView.addKeyframe(withRelativeStartTime: 3/4, relativeDuration: 1/4) {
                    toVC.loginButton.layer.transform = CATransform3DIdentity
                }
        },
            completion: { _ in
                if transitionContext.transitionWasCancelled {
                    fromVC.view.isHidden = false
                }
                snapshot.removeFromSuperview()
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        })
    }
}

然后这里创建一个SwipeInteractiveTransition来处理swipe手势。这里用update来更新动画的完成度。

class SwipeInteractiveTransition: UIPercentDrivenInteractiveTransition {

    var isInProgress = false

    private var shouldCompleteTransition = false
    private weak var viewController: UIViewController?

    init(viewController: UIViewController) {
        super.init()
        self.viewController = viewController
        setupGestureRecognizer()
    }

    private func setupGestureRecognizer() {
        let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
        gesture.edges = .left
        viewController?.view.addGestureRecognizer(gesture)
    }

    @objc private func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
        var progress = (translation.x / 200)
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

        switch gestureRecognizer.state {
        case .began:
            isInProgress = true
            viewController?.dismiss(animated: true, completion: nil)
        case .changed:
            shouldCompleteTransition = progress > 0.5
            update(progress)
        case .ended:
            isInProgress = false
            if shouldCompleteTransition {
                finish()
            } else {
                cancel()
            }
        default:
            isInProgress = false
            cancel()
        }
    }
}

然后再加上两个回调方法。

   func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard let giftVC = dismissed as? GiftViewController, let interactiveTransition = giftVC.swipeTransition else { return nil }
        return DismissViewControllerAnimation(destinationFrame: loginButton.frame, interactionController: interactiveTransition)
    }

    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if let destinationVC = animator as? DismissViewControllerAnimation, let interactionVC = destinationVC.interactiveControl, interactionVC.isInProgress {
            return interactionVC
        } else {
            return nil
        }
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值