在iOS開(kāi)發(fā)中峻堰,用UINavigationController可以push出一個(gè)界面(UIViewController)挽铁,但不能push出一個(gè)UINavigationController。
如果嘗試下這么做霜旧,應(yīng)用會(huì)拋出異常撩穿,并給出log提示:
reason: 'Pushing a navigation controller is not supported'
因?yàn)橐恍┢婀值漠a(chǎn)品、交互需求萍倡,或者是舊有的界面層級(jí)問(wèn)題,非要在一個(gè)已有的UINavigationController中以從左到右的動(dòng)畫(huà)方式(即系統(tǒng)默認(rèn)的push動(dòng)畫(huà)樣式)展示一個(gè)UINavigationController辟汰,該如何實(shí)現(xiàn)呢列敲?
解決思路:
UINavigationController雖然不可以push另一個(gè)UINavigationController,但是可以通過(guò)以下兩種方式“展示”另一個(gè)UINavigationController:
- addChildViewController直接add一個(gè)UINavigationController帖汞,并將其view也add到原有的UINavigationController的view層級(jí)上戴而;
- presentViewController方式present出一個(gè)完整的UINavigationController結(jié)構(gòu);
上面兩種方式都可以作為解決思路翩蘸,進(jìn)行自定義動(dòng)畫(huà)所意,實(shí)現(xiàn)這個(gè)“Pushing a navigation controller”的需求。
第一種方式?jīng)]嘗試過(guò)催首,可能會(huì)在UINavigationBar顯示等方面出現(xiàn)坑扶踊。
第二種方式的presentViewController已有成熟的自定義動(dòng)畫(huà)、手勢(shì)API支持郎任,實(shí)現(xiàn)起來(lái)更方便秧耗,本文將以第二種方式實(shí)現(xiàn)該效果。
實(shí)現(xiàn)要求:
- 將present動(dòng)畫(huà)自定義為系統(tǒng)原生push樣式的動(dòng)畫(huà)舶治;
- 支持手指跟隨的右滑返回手勢(shì)分井;
- present后,兩個(gè)UINavigationController原有返回手勢(shì)不受影響霉猛;
實(shí)現(xiàn)效果:
圖中在NavA上的VCA present出了NavB(topViewController為VCB)尺锚,顯示的效果是VCA push出了VCB。
實(shí)現(xiàn)代碼:https://github.com/kamous/NavigationPresent
實(shí)現(xiàn)步驟:
自定義present動(dòng)畫(huà)
- 分別為present和dismiss創(chuàng)建實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning協(xié)議的動(dòng)畫(huà)類(lèi):PPTransitionPresenPushStyleAnimator及PPTransitionDismissPopStyleAnimator韩脏。
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
在UIViewControllerAnimatedTransitioning協(xié)議的上面這個(gè)回調(diào)中缩麸,從transitionContext對(duì)象可獲取present和dissmiss相關(guān)的兩個(gè)UIViewController,以及動(dòng)畫(huà)的畫(huà)布——containerView,在回調(diào)內(nèi)完成自定義的動(dòng)畫(huà)杭朱,PPTransitionDismissPopStyleAnimator的實(shí)現(xiàn)如下:
#define kPPTransitionDismissPopStyleDuration 0.3
@implementation PPTransitionDismissPopStyleAnimator
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return kPPTransitionDismissPopStyleDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGRect fromVCRect = fromVC.view.frame;
fromVCRect.origin.x = 0;
fromVC.view.frame = fromVCRect;
[container addSubview:toVC.view];
CGRect toVCRect = toVC.view.frame;
toVCRect.origin.x = -screenWidth;
toVC.view.frame = toVCRect;
fromVCRect.origin.x = screenWidth;
toVCRect.origin.x = 0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromVC.view.frame = fromVCRect;
toVC.view.frame = toVCRect;
} completion:^(BOOL finished){
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];//動(dòng)畫(huà)結(jié)束阅仔、取消必須調(diào)用
}];
}
@end
PPTransitionDismissPopStyleAnimator實(shí)現(xiàn)也與之類(lèi)似,具體見(jiàn)源碼弧械。
- 調(diào)用presentViewController:animated:completion:消息的類(lèi)八酒,需要實(shí)現(xiàn)UIViewControllerTransitioningDelegate協(xié)議,以返回剛才定義的自定義動(dòng)畫(huà)對(duì)象刃唐。
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source {
return [PPTransitionPresenPushStyleAnimator new];
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return [PPTransitionDismissPopStyleAnimator new];
}
自定義返回手勢(shì)
- 為即將被present出來(lái)的view添加UIScreenEdgePanGestureRecognizer手勢(shì)作為返回操作手勢(shì)羞迷。
該類(lèi)型手勢(shì)與UINavigationController自帶的返回手勢(shì)interactivePopGestureRecognizer是同一類(lèi)型,二者應(yīng)該是互斥的画饥,同一時(shí)間只識(shí)別其中一個(gè)衔瓮。所以添加時(shí)需增加requireGestureRecognizerToFail的邏輯:
UIScreenEdgePanGestureRecognizer *screenGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(onPanGesture:)];
screenGesture.delegate = self;
screenGesture.edges = UIRectEdgeLeft;
[viewControllerToPresent.view addGestureRecognizer:screenGesture];
if ([viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
[screenGesture requireGestureRecognizerToFail:((UINavigationController*)viewControllerToPresent).interactivePopGestureRecognizer];
}
- 實(shí)現(xiàn)pan手勢(shì)手指跟隨,即手指從左向右邊滑動(dòng)后抖甘,又向左邊屏幕滑動(dòng)热鞍,則需要取消此次的dismiss操作。
UIViewControllerTransitioningDelegate的回調(diào)接口已有對(duì)這種情況的支持衔彻,只需要返回在下面兩個(gè)回調(diào)中薇宠,返回一個(gè)實(shí)現(xiàn)UIViewControllerInteractiveTransitioning協(xié)議的對(duì)象即可。
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
而系統(tǒng)自帶的UIPercentDrivenInteractiveTransition類(lèi)已實(shí)現(xiàn)該協(xié)議艰额,它的功能描述如下:
A percent-driven interactive transition object drives the custom animation between the disappearance of one view controller and the appearance of another.
所以此處使用UIPercentDrivenInteractiveTransition類(lèi)完成這個(gè)進(jìn)度相關(guān)的判斷澄港,將UIPercentDrivenInteractiveTransition對(duì)象以屬性方式聲明在ViewController中。
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *percentDrivenTransition;
在pan手勢(shì)處理的方法中完成對(duì)該屬性的初始化和邏輯處理:
- (void)onPanGesture:(UIScreenEdgePanGestureRecognizer *)gesture {
float progress = [gesture translationInView:self.view].x / [UIScreen mainScreen].bounds.size.width;
if (gesture.state == UIGestureRecognizerStateBegan) {
self.percentDrivenTransition = [UIPercentDrivenInteractiveTransition new];
[self dismissViewControllerAnimated:YES completion:NULL];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
[self.percentDrivenTransition updateInteractiveTransition:progress];
} else if (gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateEnded) {
if (progress > 0.5) {
[self.percentDrivenTransition finishInteractiveTransition];
} else {
[self.percentDrivenTransition cancelInteractiveTransition];
}
self.percentDrivenTransition = nil;
}
}
至此柄沮,就完成了自定義push樣式的present動(dòng)畫(huà)回梧。
Demo下載