自定義轉場動畫
- 個人理解為重寫了被彈出控制器的modal樣式,根據自己的樣式來顯示modal出來的控制器
例:presentViewController(aVC, animated: true, completion: nil)
1.為了實現如圖,modal出來的aVC控制器有下圖這樣的效果
首先,需要在modal出來之前,設置aVC的自定義轉場動畫的樣式為自定義(UIModalPresentationStyle.Custom)
其次,設置自定義轉場動畫的代理,協議
UIViewControllerTransitioningDelegate
(代理可以是當前控制器,項目中為了減少當前控制器的代碼量,單獨抽取了一個類)
// 2.創建菜單
let sb = UIStoryboard(name: "PopoverMenuViewController", bundle: nil)
let menuVC = (sb.instantiateInitialViewController())!
// 自定義轉場動畫
// 2.1設置自定義轉場動畫的樣式
menuVC.modalPresentationStyle = UIModalPresentationStyle.Custom
// 2.2設置轉場動畫的代理
menuVC.transitioningDelegate = presentationMgr
// 3.彈出菜單,modal默認的彈出樣式是從上往下,而且還不能控制彈出控制器的大小
presentViewController(menuVC, animated: true, completion: nil)
2.UIViewControllerTransitioningDelegate協議中的三個方法
- 方法一(返回一個負責轉場動畫的對象,轉場動畫完成后顯示的尺寸和位置,可以在這個負責轉場動畫對象中設置,
所以說要自定義這個類,在自定義的負責轉場動畫的類中設置自己需要的位置和尺寸
) :
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
{
// 自定義負責轉場的對象ChaosPresentationController
let pc = ChaosPresentationController(presentedViewController: presented, presentingViewController: presenting)
// 位置由外界決定
pc.presentViewFrame = presentViewFrame
return pc
}
2.1自定義自定義負責轉場的對象ChaosPresentationController : UIPresentationController,也就是自定義轉場.作用:
1.如果不自定義轉場,modal出來的控制器會移除原有的控制器
2.如果不自定義轉場,modal出來的控制器不會移除原有的控制器
3.如果不自定義轉場,modal出來的控制器的尺寸和屏幕一樣
4.如果自定義轉場,modal出來的控制器的尺寸可以在containerViewWillLayoutSubviews方法中控制
5.containerView 非常重要,容器試圖,所有modal出來的視圖都添加到了containerView上
6.presentedView() 非常重要,通過該方法能夠拿到彈出的視圖
- 決定彈出視圖位置的實現思路有兩種
// 第一種 : 重寫containerViewWillLayoutSubviews方法,用於布局轉場動畫彈出的控件
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
// containerView 非常重要,容器試圖
// presentedView() 非常重要,通過該方法能夠拿到彈出的視圖
presentedView()?.frame = presentViewFrame // 位置尺寸有外界來決定
// 將HUD添加到containerView上
containerView?.insertSubview(HUD, atIndex: 0)
}
// 第二種 : 重寫frameOfPresentedViewInContainerView方法,返回容器中view的frame
override func frameOfPresentedViewInContainerView() -> CGRect {
return CGRect(x: 85, y: 50, width: 200, height: 300)
}
- 方法二:
控制器出現的時候會調用該方法
該方法用於返回一個負責轉場動畫如何出現的對象,需要遵守UIViewControllerAnimatedTransitioning
協議
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// 返回當前控制器,讓當前控制器來負責轉場怎么出現,並且當前控制器要遵循UIViewControllerAnimatedTransitioning協議
ChaosLog("出現")
// 發送通知,告訴外界狀態發生改變
NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
isPresent = true // 改變標記的值
return self
}
- 方法三:
控制器隱藏的時候會調用該方法
該方法用於返回一個負責轉場動畫如何消失的對象,需要遵守UIViewControllerAnimatedTransitioning
協議
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// 返回當前控制器,讓當前控制器來負責轉場怎么消失,並且當前控制器要遵循UIViewControllerAnimatedTransitioning協議
ChaosLog("消失")
// 發送通知,告訴外界狀態發生改變
NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
isPresent = false // 改變標記的值
return self
}
3.UIViewControllerAnimatedTransitioning協議中的兩個重要方法
- 方法一 : 用來告訴系統展示動畫和消失動畫所用的時長,用來統一轉場動畫顯示和消失所用的時間
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.3
}
重點
方法二 : 專門用於管理modal以什么樣的動畫展現和消失的,無論是展現還是消失都會調用該方法
<1> 專門用於管理modal以什么樣的動畫顯示
和消失
的,無論展示和消失都會調用這個方法
<2> 只要實現了這個方法,系統就不會默認添加動畫了(modal系統默認動畫是從上往下鑽) ❤️> 所有的動畫效果都需要我們自己實現,包括需要展示的視圖也需要我們自己添加到容器的視圖上(containerView)
<4> transitionContext
: 所有動畫需要的屬性等都保存在上下文中,換而言之就是可以通過transitionContext獲取我們想要的東西
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
{
if isPresent { // 顯示
willPresentViewController(transitionContext)
} else { // 消失
willDismissViewController(transitionContext)
}
}
// 顯示視圖的方法
private func willPresentViewController(transitionContext: UIViewControllerContextTransitioning) {
// // 將要modal出來的VC
// let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
// // 控制toVC顯示出來的VC
// let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
// ChaosLog(toVC)
// ChaosLog(fromVC)
// 對應toVC的View
guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
return
}
// 對應fromVC的View
// let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
// 獲取containerView
transitionContext.containerView()?.addSubview(toView)
// 動畫效果
toView.transform = CGAffineTransformMakeScale(1.0, 0.0)
// layer的錨點默認在中間,動畫也是從中間開始,將錨點設置為頂部
toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
toView.transform = CGAffineTransformIdentity
}) { (_) in
// 注意: 自定義轉場動畫,在執行完動畫之后一定要告訴系統動畫執行完畢了
// 剛開始自己忘了,然后PopoverMenu上的TableView不見了
transitionContext.completeTransition(true)
}
}
// dismiss視圖的方法
private func willDismissViewController(transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else {
return
}
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
// menu沒有動畫消失,而是立即消失的原因是CGFloat不准確,導致動畫無法執行.
// 解決方案: 給CGFloat設置一個很小的值即可
fromView.transform = CGAffineTransformMakeScale(1.0, 0.00001)
}, completion: { (_) -> Void in
transitionContext.completeTransition(true)
})
}
自定義轉場動畫細節問題
1. 導航欄的titlesView中箭頭方向改變的問題 : 剛開始的做法是根據選中狀態,修改箭頭的位置.但是項目中的實際效果為,點擊titlesView出現菜單控制器,點擊除了titlesView的任何地方菜單控制器消失,如圖
- 解決方案 : 在代理方法中有發出通知,外界通過監聽通知來改變titlesView中箭頭的方向
- 通知的字符串常量
- 分別在菜單控制器顯示和消失的方法中發出通知
- 外界通過監聽通知來改變titlesView中箭頭方向
- 通知的字符串常量
2. 自定義轉場動畫結束后,一定要告訴系統,動畫結束
// 注意: 自定義轉場動畫,在執行完動畫之后一定要告訴系統動畫執行完畢了
// 剛開始自己忘了,然后PopoverMenu上的TableView不見了
transitionContext.completeTransition(true)