先從一個簡單的轉場動畫開始,了解自定義轉場動畫的過程。
第一個畫面背景是綠色的,中間有個紅色的箱子,當點下紅色的箱子以後,箱子會擴散出紅色至整個畫面。
第二個畫面背景是紅色的,中間有個綠色的箱子,當點下綠色的箱子以後,紅色的背景會縮到中間的箱子當中。
點下箱子以後會去 present 第二個畫面,這裡比一般的 present 多了兩個設定,讓 ViewController 知道我們要自定義轉場動畫。
let VC = DetailViewController()
VC.modalPresentationStyle = .custom
VC.transitioningDelegate = diffustionTransition
present(VC, animated: true, completion: nil)
另外我們會告訴 diffusionTransition 我們要在哪一個 View 上做動畫(從那裡擴散和從那裡收縮)
這個畫面的例子就是中間的按鈕了。
diffustionTransition = SKDiffussionTransition(animatedView: button)
繼承於 NSObject, UIViewControllerAnimatedTransitioning
在這個方法中返回動畫的 duration
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
另外會實作 UIViewControllerTransitioningDelegate 中實現兩個方法。
通過改變自己定義的 isReverse 屬性,讓 SKDiffusionTransition 知道目前是要做 present 還是 dismiss 的動畫效果。
extension SKDiffussionTransition: UIViewControllerTransitioningDelegate {
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isReverse = false
return self
}
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isReverse = true
return self
}
}
具體的轉場動畫內容都會在這個方法中實現
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// get frame and backgroundColor
var startFrame = CGRect.zero
if animatedView != nil {
startFrame = animatedView!.frame
startBackgroundColor = animatedView!.backgroundColor
}
// init animated view for transition
let animatedViewForTransition = UIView(frame: startFrame)
animatedViewForTransition.clipsToBounds = true
animatedViewForTransition.layer.cornerRadius = animatedViewForTransition.frame.height / 2.0
animatedViewForTransition.backgroundColor = self.startBackgroundColor
// add animated view on transitionContext's containerView
transitionContext.containerView.addSubview(animatedViewForTransition)
// set presentedController
let presentedController: UIViewController
if !isReverse {
presentedController = transitionContext.viewController(forKey: .to)!
presentedController.view.layer.opacity = 0
} else {
presentedController = transitionContext.viewController(forKey: .from)!
}
presentedController.view.frame = transitionContext.containerView.bounds
transitionContext.containerView.addSubview(presentedController.view)
let size = max(transitionContext.containerView.frame.height, transitionContext.containerView.frame.width) * 1.2
let scaleFactor = size / animatedViewForTransition.frame.width
let finalTransform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
if !self.isReverse {
UIView.transition(with: animatedViewForTransition,
duration: self.transitionDuration(using: transitionContext) * 0.7,
options: [],
animations: {
animatedViewForTransition.transform = finalTransform
animatedViewForTransition.center = transitionContext.containerView.center
animatedViewForTransition.backgroundColor = presentedController.view.backgroundColor
},completion: nil)
UIView.animate(withDuration: self.transitionDuration(using: transitionContext) * 0.4,
delay: self.transitionDuration(using: transitionContext) * 0.6,
animations: {
presentedController.view.layer.opacity = 1
},completion: { (_) in
animatedViewForTransition.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else {
animatedViewForTransition.transform = finalTransform
animatedViewForTransition.center = transitionContext.containerView.center
animatedViewForTransition.backgroundColor = presentedController.view.backgroundColor
UIView.animate(withDuration: self.transitionDuration(using: transitionContext) * 0.7, animations: {
presentedController.view.layer.opacity = 0
})
DispatchQueue.main.asyncAfter(deadline: .now() + self.transitionDuration(using: transitionContext) * 0.3) {
UIView.transition(with: animatedViewForTransition,
duration: self.transitionDuration(using: transitionContext) * 0.6,
options: [],
animations: {
animatedViewForTransition.transform = CGAffineTransform.identity
animatedViewForTransition.backgroundColor = self.startBackgroundColor
animatedViewForTransition.frame = startFrame
},
completion: { (_) in
animatedViewForTransition.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}
}