蘋果在 iOS10 里重新設(shè)計(jì)了音樂(lè)播放器(Music),當(dāng)時(shí)在設(shè)計(jì)圈子還是個(gè)不小的事情。對(duì)于我個(gè)人而言,很喜歡它的「正在播放」界面的轉(zhuǎn)場(chǎng)方式。最近打算對(duì) OneShare 進(jìn)行最后的升級(jí),正好借鑒了這個(gè)轉(zhuǎn)場(chǎng)。發(fā)現(xiàn)步驟雖然不難,但還是有點(diǎn)繁瑣,有些細(xì)節(jié)需要注意一下。
在 iOS 中,自定轉(zhuǎn)場(chǎng)是通過(guò)實(shí)現(xiàn)協(xié)議 UIViewControllerTransitioningDelegate
來(lái)實(shí)現(xiàn)的。協(xié)議中提供了 TransitionAnimator(轉(zhuǎn)場(chǎng)動(dòng)畫)、InteractiveAnimator(轉(zhuǎn)場(chǎng)交互)和 PresentationController(視圖層級(jí)控制)相關(guān)的方法。
// TransitionAnimator(轉(zhuǎn)場(chǎng)動(dòng)畫)
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
// InteractiveAnimator(轉(zhuǎn)場(chǎng)交互)
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator;
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator;
// PresentationController(視圖層級(jí)控制)
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;
大多數(shù)情況下,我們只會(huì)用 TransitionAnimator 那兩個(gè)方法來(lái)實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫。再?gòu)?fù)雜一點(diǎn),我們希望實(shí)現(xiàn)自定手勢(shì)交互的時(shí)候,就需要重寫 InteractiveAnimator 那兩個(gè)方法了。分析蘋果的交互,會(huì)發(fā)現(xiàn)它還修改了視圖層級(jí)的關(guān)系。所以接下來(lái)模仿此交互的步驟大概就是:
- 實(shí)現(xiàn)
UIViewControllerAnimatedTransitioning
自定義轉(zhuǎn)場(chǎng)動(dòng)畫; - 實(shí)現(xiàn)
UIPresentationController
自定義視圖控制器的層級(jí)表現(xiàn); - 實(shí)現(xiàn)
UIViewControllerInteractiveTransitioning
自定義交互方式。
所以首先在遵守 UIViewControllerTransitioningDelegate
協(xié)議的 AnimatorController
中,直接通過(guò) UIView
的 animateWithDuration
方法去自定義轉(zhuǎn)場(chǎng)動(dòng)畫即可,這一步并沒(méi)什么難度。
接下來(lái)實(shí)現(xiàn)視圖層級(jí)控制之前,首先需要將被推出的視圖(這里是 ModalViewController
)的 modalPresentationStyle
設(shè)置為自定義(UIModalPresentationCustom
)。這樣才會(huì)調(diào)用協(xié)議中的 presentationControllerForPresentedViewController
方法。
實(shí)現(xiàn) UIPresentationController
時(shí),由于它同時(shí)接管了轉(zhuǎn)入(presentation)和轉(zhuǎn)出(dismissal),所以要在步驟 1 的基礎(chǔ)上增加一個(gè)轉(zhuǎn)出動(dòng)畫。這時(shí)可以另外新建一個(gè)類來(lái)處理轉(zhuǎn)出動(dòng)畫,也可以直接在上面那個(gè) animateTransition:
中實(shí)現(xiàn),我使用的時(shí)后者。
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;
};
}
最后就是實(shí)現(xiàn)交互了,因?yàn)槭怯?jì)劃打算使用下拉手勢(shì),所以自定義的 AnimatorInteractive
是繼承的 UIPercentDrivenInteractiveTransition
,通過(guò)百分比來(lái)控制視圖的動(dòng)效。這里要注意在重寫協(xié)議中的 interactionControllerForDismissal
方法時(shí),不是通過(guò)手勢(shì)轉(zhuǎn)出的時(shí)候,要返回 nil
,否則協(xié)議不會(huì)去調(diào)用 animateTransition:
。
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
return self.dismissInteractor.interactionInProgress ? self.dismissInteractor : nil;
}
另外由于在轉(zhuǎn)場(chǎng)中使用了 CGAffineTransformMakeScale
轉(zhuǎn)入,所以在轉(zhuǎn)出時(shí),需要設(shè)置 toViewController.view.transform = CGAffineTransformIdentity
。這時(shí)候遇到了個(gè)麻煩,在使用下拉手勢(shì)的時(shí)候,父視圖在交互過(guò)程中,就已經(jīng)完成整個(gè)變化,導(dǎo)致取消下拉的時(shí)候,也不會(huì)復(fù)原。因?yàn)闆](méi)有找到解決辦法,仔細(xì)觀察了下蘋果的交互,發(fā)現(xiàn)也在下拉的時(shí)候父試圖也沒(méi)有動(dòng)效,所以猜測(cè)沒(méi)有直接解決辦法,最后就改成了音樂(lè)播放器這樣的實(shí)現(xiàn)方式。
這里提一句在使用 Swift 實(shí)現(xiàn)的時(shí)候,iOS10 上通過(guò)下拉手勢(shì)關(guān)閉試圖沒(méi)成功的時(shí)候,ModalViewController
可能不會(huì)復(fù)原,這可能是蘋果在 iOS10 改了 UIView.animateTransition
實(shí)現(xiàn)的原因,用 UIViewPropertyAnimator
的 runningPropertyAnimator
方法替代就可以了,在之前的一篇博客中提到過(guò)。
到這兒基本是就完了,還有最后一點(diǎn)修飾工作,就是 StatusBar 的樣式。其實(shí)設(shè)置起來(lái)不麻煩,在 ModalViewController
里如下設(shè)置即可:
- (void)viewDidLoad {
[super viewDidLoad];
self.modalPresentationCapturesStatusBarAppearance = YES;
// Do any additional setup after loading the view.
...
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
但在實(shí)際操作中遇到了一點(diǎn)麻煩,因?yàn)椴恢朗裁磿r(shí)候把 Info.plist
里的 UIViewControllerBasedStatusBarAppearance
設(shè)置為 NO
了,所以一直沒(méi)有成功,最后看到這篇文章才意識(shí)到問(wèn)題。總結(jié)起來(lái)決定 StatusBar 樣式的優(yōu)先級(jí)依次是:
Info.plist > UINavigationController > Modal 中的 preferredStatusBarStyle
此文示例代碼:iOS10ModelPresent
參考鏈接: