Presentation Controller
我們來改變系統自帶的present動畫,主要用到UIViewControllerTransitioningDelegate的代理方法animationController(forPresented:presenting:source:),該代理方法需要返回一個UIViewControllerAnimatedTransitioning的可選對象,當返回nil時,UIKit將使用內置系統默認動畫,當不爲nil時,將使用你自己自定義的動畫效果。
現在有這樣一個場景:
我們在點擊底下image時,需要將圖片scale到一個新的界面,而不是採用系統默認的present動畫從屏幕底部開始覆蓋。
present轉場動畫
1.我們創建一個動畫構造器,命名爲PopAnimator.swift
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
}
2.在FirstViewController裏面實現UIViewControllerTransitioningDelegate的代理方法
class ViewController: UIViewController {
let transition = PopAnimator()
...
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}
3.設置SecondViewController的過渡代理
secondVC.transitioningDelegate = self
上面做了一些基本的初始化,現在我們來實現我們的動畫構造器PopAnimator.swift。
首先爲PopAnimator添加三個屬性:
let duration = 1.0//動畫時間
var presenting = true//用來判斷是present還是dismiss
var originFrame = CGRect.zero//用來存儲image的原始位置
替換transitionDuration()的返回值爲
return duration
現在我們來爲animateTransition添加動畫,它有一個UIViewControllerContextTransitioning類型的參數。在這之前,你需要了解動畫上下文。
當present轉場動畫開始時,firstView將被添加到轉場容器(container)裏面,secondView將被創建但是屏幕上不可見,因此我們主要做的就是爲容器add一個secondView並賦予進入的動畫,爲firstView添加移除的動畫。默認情況下,firstView並不需要我們remove,動畫完成後會自動從container容器中移除。
我們來創建一個簡單的淡入動畫來測試一下。
添加以下代碼到animateTransition()
let containerView = transitionContext.containerView//獲得動畫的容器
let toView = transitionContext.view(forKey: .to)!//取得secondView
//let toViewController = transitionContext.viewController(forKey: .to)!//取secondViewController
containerView.addSubview(toView)//添加secondView到容器裏面
toView.alpha = 0.0//設置secondView初始透明度爲0
UIView.animate(withDuration: duration, animations: {
toView.alpha = 1.0
}) { (_) in
transitionContext.completeTransition(true)//告訴UIKit轉場動畫已經完成,轉場完成時,firstView會自動從container容器中移除
}
這樣就實現了淡入的轉場動畫
pop轉場動畫
替換animateTransition()裏面的代碼:
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let secondView = presenting ? toView : transitionContext.view(forKey: .from)!
當present時,secondView是toView,當pop時,secondView變成了fromView。
設置secondView動畫前座標及放大倍數
let initialFrame = presenting ? originFrame : secondView.frame
let finalFrame = presenting ? secondView.frame : originFrame
let xScaleFactor = presenting ? initialFrame.width / finalFrame.width : finalFrame.width / initialFrame.width
let yScaleFactor = presenting ? initialFrame.height / finalFrame.height : finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
secondView.transform = scaleTransform
secondView.center = CGPoint(x: initialFrame.midX, y: initialFrame.midY)
secondView.clipsToBounds = true
}
添加動畫
containerView.addSubview(toView)
containerView.bringSubview(toFront: secondView)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, animations: {
secondView.transform = self.presenting ? CGAffineTransform.identity : scaleTransform
secondView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
secondView.layer.cornerRadius = self.presenting ? 0.0 : 20.0/xScaleFactor
}) { (_) in
transitionContext.completeTransition(true)
}
在FirstViewController中的animationController(forPresented:)方法裏面設置PopAnimator的初始值,present動畫就做好了。
transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)
transition.presenting = true
selectedImage!.isHidden = true
在animationController(forDismissed:)裏面設置PopAnimator初始值,dismiss動畫就做好了。
transition.presenting = false
return transition
在dismiss動畫完成後,需要將selectImage取消隱藏
爲PopAnimator.swift添加一個block
var dismissCompletion: (()->Void)?
在completeTransition()之前調用
if !self.presenting {
self.dismissCompletion?()
}
在FirstViewController的viewDidLoad裏面添加回調顯示selectImage
transition.dismissCompletion = {
self.selectedImage!.isHidden = false
}
Orientation動畫
設備方向改變時,我們在FirstViewController裏重新設置原Image的位置及尺寸
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
self.bgImage.alpha = (size.width > size.height) ? 0.25 : 0.55
self.positionListItems()//重新佈局
}, completion: nil)
}
運行效果