博客> iOS 从设计角度分析和封装UINavigationController的转场代理
iOS 从设计角度分析和封装UINavigationController的转场代理
2019-10-23 05:50 评论:0 阅读:1427 chenxm2
ios 视图控制器转场 UINavigationControllerDelegate

前言

对于分析和讲解视图控制器转场的文章网上已经有很多。 相关的好文例如: Enter your link description here: 本文并非是要重复分析并讲解。本文重点从代码设计的角度讨论分析UINavigationController的转场代理。

文章概要

  • 情景分析&问题描述
  • 解决问题
  • 结束语

1. 情景分析&问题描述

 1D751179-2DBC-4E69-9992-4F3F0C2234B1.png 对照上图,通常我们应用中会有以下其中一些情况(并不包括所有) UINavigationController C中会被push进一些UIViewController 如E,F,G. UINavigationController C可能是被UIViewController A present 出来的,也可能是UITabBarController B中的一个tab。

那么当把F push 到 C的时候,想自定义转场动画,通常的做法就是A或B去实现C的 UINavigationControllerDelegate的以下这个方法(为了方便我在文章后续部分称这个方法为Funcf

  • (nullable id )navigationController:(UINavigationController )navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController )fromVC toViewController:(UIViewController *)toVC

(一) 如果在我们的app中所有Push和Pop的自定义动画都一样,那么只需要在这个接口上返回满足UIViewControllerAnimatedTransitioning协议的Push和Pop的动画实例. 问题1.这种情况,我们还是需要在所有要实现UINavigationControllerDelegate这个接口的地方返回动画实例。

(二)假设Push F 和Push G时其实是需要不一样的Push动画。那么我们有两种方法去做: (1)在Funcf中通过判断fromVC及toVC为哪个类到返回不同的Push动画实例。 if ([fromVC isKindOfClass:[E class]] && [toVC isKindOfClass:[F class]]) { ... return AnimationF } else if ([fromVC isKindOfClass:[F class]] && [toVC isKindOfClass:[G class]]) { return AnimationG } (2)进到同一个动画实例再区分,在Funcf中直接返回 AnimationBoth对像.此时 AnimationBoth的代码片段应该是这样的 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; if ([fromVC isKindOfClass:[E class]] && [toVC isKindOfClass:[F class]]) { ...自定义动画1 } else if ([fromVC isKindOfClass:[F class]] && [toVC isKindOfClass:[G class]]) { ...自定义动画2 } }

问题2.这样就需要在实现代理的类中A或B又或者动画实例中(更不可取)去做这样的if else判断。正常情况下 F是从E中触发并被创建出来,而G会是从F中。A或B根本就没必要知道E,F,G的存在。

1. 解决问题

首先解决问题2。 解决问题的方向,如果能做到能把返回自定义动画的Funcf的放到对应的UIViewController中去做,如push F和 pop F 时能把Fucf的实现放到E(为什么不是放到F中自行去理解)中去实现返回自定义动画。如push G和 pop G时能把Fucf的实现放到F中去实现返回自定义动画。那么就不需要在A或B中进行if else 判断。也不存在A或B需要知到E,F,G的存在了。

实现思路及关键代码,新建TTNavigationController 继承于UINavigationController。 重写setDelegate方法,把代理设置为UINavigationControllerDeleagteInterceptor,代理拦截器。 - (void)setDelegate:(id<UINavigationControllerDelegate>)delegate { if (_interceptor == nil) { _interceptor = [[UINavigationControllerDeleagteInterceptor alloc] init]; } _interceptor.realDelegate = delegate; _interceptor.navigationController = self; [super setDelegate:(id)_interceptor]; }

UINavigationControllerDeleagteInterceptor 的完整代码。在TTNavigationController中实现Funcf,UINavigationControllerDeleagte的Funcf就会被拦截并调到TTNavigationController中的Funcf。在 TTNavigationController 没有实现的方法就会被UINavigationControllerDeleagteInterceptor重新调回到真正的代理中。 @interface UINavigationControllerDeleagteInterceptor : NSObject @property (nonatomic, weak) id realDelegate; //the readDeleagte set to TTNavigationController @property (nonatomic, weak) id navigationController; //is TTNavigationController @end

@implementation UINavigationControllerDeleagteInterceptor - (id)forwardingTargetForSelector:(SEL)aSelector { if ([_navigationController respondsToSelector:aSelector]) { return _navigationController; } if ([_realDelegate respondsToSelector:aSelector]) { return _realDelegate; } return [super forwardingTargetForSelector:aSelector]; }

`- (BOOL)respondsToSelector:(SEL)aSelector { if ([_navigationController respondsToSelector:aSelector]) { return YES; } if ([_realDelegate respondsToSelector:aSelector]) { return YES; } return [super respondsToSelector:aSelector]; } @end

然后在TTNavigationController这样实现Funcf,就可以调用到对应的UIVIewController中 `- (nullable id )navigationController:(UINavigationController )navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController )fromVC toViewController:(UIViewController )toVC { if ([fromVC respondsToSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)] || [toVC respondsToSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]) { __autoreleasing id animator = nil; id target = nil; if (operation == UINavigationControllerOperationPush && [fromVC respondsToSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]) { target = fromVC; } else if (operation == UINavigationControllerOperationPop && [toVC respondsToSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]) { target = toVC; } if (target) { NSMethodSignature methodSignature = [target methodSignatureForSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; [invocation setSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]; [invocation setTarget:target]; [invocation setArgument:&navigationController atIndex:2]; [invocation setArgument:&operation atIndex:3]; [invocation setArgument:&fromVC atIndex:4]; [invocation setArgument:&toVC atIndex:5]; [invocation invoke]; [invocation getReturnValue:&animator]; return animator; } }

再解决问题1 这时候可以通过写一个继承于UIViewController的基类实现Funcf,在基类做一个默认的自定义动画,其它UIViewController都继际于它,如果不实现Funcf那么就会用类的,如果实现了,就会用回自己的实现。 但我更倾向于写个UIViewController的类别去实现,并在实现中检查主类是否有实现,如果有就调回主类的,这样也能实现写个公共基类的效果(我就是这么做的)。

结束语

以上所写的都是一些关键点。具体完整代码可参考demo Enter your link description here:

收藏
0
sina weixin mail 回到顶部