蘋果在 iOS10 里重新設計了音樂播放器(Music),當時在設計圈子還是個不小的事情讥电。對于我個人而言蹂窖,很喜歡它的「正在播放」界面的轉(zhuǎn)場方式。最近打算對 OneShare 進行最后的升級恩敌,正好借鑒了這個轉(zhuǎn)場瞬测。發(fā)現(xiàn)步驟雖然不難,但還是有點繁瑣纠炮,有些細節(jié)需要注意一下月趟。
在 iOS 中,自定轉(zhuǎn)場是通過實現(xiàn)協(xié)議 UIViewControllerTransitioningDelegate
來實現(xiàn)的恢口。協(xié)議中提供了 TransitionAnimator(轉(zhuǎn)場動畫)孝宗、InteractiveAnimator(轉(zhuǎn)場交互)和 PresentationController(視圖層級控制)相關的方法。
// TransitionAnimator(轉(zhuǎn)場動畫)
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
// InteractiveAnimator(轉(zhuǎn)場交互)
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator;
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator;
// PresentationController(視圖層級控制)
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;
大多數(shù)情況下耕肩,我們只會用 TransitionAnimator 那兩個方法來實現(xiàn)轉(zhuǎn)場動畫因妇。再復雜一點问潭,我們希望實現(xiàn)自定手勢交互的時候沙峻,就需要重寫 InteractiveAnimator 那兩個方法了。分析蘋果的交互两芳,會發(fā)現(xiàn)它還修改了視圖層級的關系摔寨。所以接下來模仿此交互的步驟大概就是:
- 實現(xiàn)
UIViewControllerAnimatedTransitioning
自定義轉(zhuǎn)場動畫竖螃; - 實現(xiàn)
UIPresentationController
自定義視圖控制器的層級表現(xiàn)淑廊; - 實現(xiàn)
UIViewControllerInteractiveTransitioning
自定義交互方式。
所以首先在遵守 UIViewControllerTransitioningDelegate
協(xié)議的 AnimatorController
中特咆,直接通過 UIView
的 animateWithDuration
方法去自定義轉(zhuǎn)場動畫即可季惩,這一步并沒什么難度。
接下來實現(xiàn)視圖層級控制之前腻格,首先需要將被推出的視圖(這里是 ModalViewController
)的 modalPresentationStyle
設置為自定義(UIModalPresentationCustom
)画拾。這樣才會調(diào)用協(xié)議中的 presentationControllerForPresentedViewController
方法。
實現(xiàn) UIPresentationController
時菜职,由于它同時接管了轉(zhuǎn)入(presentation)和轉(zhuǎn)出(dismissal)青抛,所以要在步驟 1 的基礎上增加一個轉(zhuǎn)出動畫。這時可以另外新建一個類來處理轉(zhuǎn)出動畫酬核,也可以直接在上面那個 animateTransition:
中實現(xiàn)蜜另,我使用的時后者。
switch (self.transitionType) {
case AnimatorDismiss: {
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
// your animation
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
break;
};
case AnimatorPresent: {
[[transitionContext containerView] addSubview:toViewController.view];
// your variable
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
// your animation
}completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
break;
};
}
最后就是實現(xiàn)交互了嫡意,因為是計劃打算使用下拉手勢举瑰,所以自定義的 AnimatorInteractive
是繼承的 UIPercentDrivenInteractiveTransition
,通過百分比來控制視圖的動效蔬螟。這里要注意在重寫協(xié)議中的 interactionControllerForDismissal
方法時嘶居,不是通過手勢轉(zhuǎn)出的時候,要返回 nil
促煮,否則協(xié)議不會去調(diào)用 animateTransition:
邮屁。
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
return self.dismissInteractor.interactionInProgress ? self.dismissInteractor : nil;
}
另外由于在轉(zhuǎn)場中使用了 CGAffineTransformMakeScale
轉(zhuǎn)入,所以在轉(zhuǎn)出時菠齿,需要設置 toViewController.view.transform = CGAffineTransformIdentity
佑吝。這時候遇到了個麻煩,在使用下拉手勢的時候绳匀,父視圖在交互過程中芋忿,就已經(jīng)完成整個變化炸客,導致取消下拉的時候,也不會復原戈钢。因為沒有找到解決辦法痹仙,仔細觀察了下蘋果的交互,發(fā)現(xiàn)也在下拉的時候父試圖也沒有動效殉了,所以猜測沒有直接解決辦法开仰,最后就改成了音樂播放器這樣的實現(xiàn)方式。
這里提一句在使用 Swift 實現(xiàn)的時候薪铜,iOS10 上通過下拉手勢關閉試圖沒成功的時候众弓,ModalViewController
可能不會復原,這可能是蘋果在 iOS10 改了 UIView.animateTransition
實現(xiàn)的原因隔箍,用 UIViewPropertyAnimator
的 runningPropertyAnimator
方法替代就可以了谓娃,在之前的一篇博客中提到過。
到這兒基本是就完了蜒滩,還有最后一點修飾工作滨达,就是 StatusBar 的樣式。其實設置起來不麻煩俯艰,在 ModalViewController
里如下設置即可:
- (void)viewDidLoad {
[super viewDidLoad];
self.modalPresentationCapturesStatusBarAppearance = YES;
// Do any additional setup after loading the view.
...
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
但在實際操作中遇到了一點麻煩弦悉,因為不知道什么時候把 Info.plist
里的 UIViewControllerBasedStatusBarAppearance
設置為 NO
了,所以一直沒有成功蟆炊,最后看到這篇文章才意識到問題稽莉。總結(jié)起來決定 StatusBar 樣式的優(yōu)先級依次是:
Info.plist > UINavigationController > Modal 中的 preferredStatusBarStyle
此文示例代碼:iOS10ModelPresent
參考鏈接: