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