原文出处:Dariel在杭州
一个app往往有很多界面,而界面之间的跳转也就是对应控制器的跳转,控制器的跳转一般有两种情况 push 或者 modal,push 和 modal 的默认效果是系统提供的,但也可以自定义.有兴趣了解一下自定义的童鞋可以看这篇,iOS动画指南 - 6.可以很酷的转场动画. 文章配图 1. 概述 系统提供的push和modal方法有时并不能满足实际需求.比如,我们需要根据服务器返回的字段跳到指定的控制器,难道作判断吗?那显然不是最佳解决方案. 其实我们可以这样:
1 2 3 4 5 | NSString *urlStr = @ "dariel://twoitem?name=dariel&userid=213213" ; // push [ DCURLRouter pushURLString:urlStr animated: YES ]; // modal [ DCURLRouter presentURLString:urlStr animated: YES completion: nil ]; |
对的,就是通过自定义URL+拼接参数,实现跳转.当然啦,DCURLRouter的功能远不止这点. 2.DCURLRouter的基本使用 DCURLRouter是一个通过简单配置就能够实现自定义URL跳转的开源组件: GitHub ps.DCURLRouter是OC版的,后续看情况可能会有swift版本的. 你的star是对我最好的支持。 1.简单集成 只要把DCURLRouter 这个文件夹拖到项目中就行了,后续会支持cocoapods . 2. 简单配置
-
每一个自定义的URL都会有一个对应的控制器,那Xocde怎么知道呢?我们需要一个plist文件.打开DCURLRouter.plist 文件 内部结构大概长这样.除了自定义的URL上面还有http 和https ,这是当如果URL是网页链接的时候,DCURLRouter会自动跳转到自定义好的webView控制器 ,并把URL当成参数传递到webView控制器.是不是很方便. 下面的dariel 字典就是用来存放自定义URL以及对应的控制器名称的.dariel 就是自定义协议头了.以后就可以把自定义的URL和对应的控制器放这里了. -
加载DCURLRouter.plist文件数据
1 2 3 4 5 6 7 8 9 | // 不需要拼接参数直接跳转 [ DCURLRouter pushURLString:@ "dariel://twoitem" animated: YES ]; // 直接把参数拼接在自定义url末尾 NSString *urlStr = @ "dariel://twoitem?name=dariel&userid=213213" ; [ DCURLRouter pushURLString:urlStr animated: YES ]; // 可以将参数放入一个字典 NSDictionary *dict = @{@ "userName" :@ "Hello" , @ "userid" :@ "32342" }; [ DCURLRouter pushURLString:@ "dariel://twoitem" query:dict animated: YES ]; // 如果当前控制器和要push的控制器是同一个,可以将replace设置为 Yes ,进行替换. [ DCURLRouter pushURLString:@ "dariel://oneitem" query:dict animated: YES replace: YES ]; // 重写了系统的push方法,直接通过控制器跳转 TwoViewController *two = [[ TwoViewController alloc] init ]; [ DCURLRouter pushViewController:two animated: YES ]; |
3. push和modal的使用 1.所有的push和modal方法都可以通过DCURLRouter这个类方法来调用.这样在push和modal的时候就不需要拿到导航控制器或控制器再跳转了.也就是说,以后push和modal控制器跳转就不一定要在控制器中进行了. push控制器
1 2 3 4 5 6 7 8 9 10 | // 不需要拼接参数直接跳转 [ DCURLRouter presentURLString:@ "dariel://threeitem" animated: YES completion: nil ]; // 直接把参数拼接在自定义url末尾 NSString *urlStr = @ "dariel://threeitem?name=dariel&userid=213213" ; [ DCURLRouter presentURLString:urlStr animated: YES completion: nil ]; // 可以将参数放入一个字典 NSDictionary *dict = @{@ "userName" :@ "Hello" , @ "userid" :@ "32342" }; [ DCURLRouter presentURLString:@ "dariel://threeitem" query:dict animated: YES completion: nil ]; // 给modal出来的控制器添加一个导航控制器 [ DCURLRouter presentURLString:@ "dariel://threeitem" animated: YES withNavigationClass:[ UINavigationController class ] completion: nil ]; // 重写了系统的push方法 ThreeViewController *three = [[ThreeViewController alloc] init]; [ DCURLRouter presentViewController:three animated: YES completion: nil ]; |
2.modal控制器 用法和push差不多,只是这里添加了一个给modal出来的控制器加一个导航控制器的方法.
1 2 3 4 5 6 7 8 9 10 | // 不需要拼接参数直接跳转 [ DCURLRouter presentURLString:@ "dariel://threeitem" animated: YES completion: nil ]; // 直接把参数拼接在自定义url末尾 NSString *urlStr = @ "dariel://threeitem?name=dariel&userid=213213" ; [ DCURLRouter presentURLString:urlStr animated: YES completion: nil ]; // 可以将参数放入一个字典 NSDictionary *dict = @{@ "userName" :@ "Hello" , @ "userid" :@ "32342" }; [ DCURLRouter presentURLString:@ "dariel://threeitem" query:dict animated: YES completion: nil ]; // 给modal出来的控制器添加一个导航控制器 [ DCURLRouter presentURLString:@ "dariel://threeitem" animated: YES withNavigationClass:[ UINavigationController class ] completion: nil ]; // 重写了系统的push方法 ThreeViewController *three = [[ThreeViewController alloc] init]; [ DCURLRouter presentViewController:three animated: YES completion: nil ]; |
4. 后退 pop 和 dismiss 在实际开发中,好几次的界面的跳转组成了一个业务流程,整个业务流程结束后通常会要求返回最开始的界面,这就要让控制器连续后退好几次,但苹果是没有提供方法的.DCURLRouter给出了具体的实现方案. pop:
1 2 3 4 5 | /** pop掉一层控制器 */ + (void)popViewControllerAnimated:( BOOL )animated; /** pop掉两层控制器 */ + (void)popTwiceViewControllerAnimated:( BOOL )animated; /** pop掉times层控制器 */ + (void)popViewControllerWithTimes:( NSUInteger )times animated:( BOOL )animated; /** pop到根层控制器 */ + (void)popToRootViewControllerAnimated:( BOOL )animated; |
dismiss:
1 2 3 4 5 | /** dismiss掉1层控制器 */ + (void)dismissViewControllerAnimated: ( BOOL )flag completion: (void (^ __nullable)(void))completion; /** dismiss掉2层控制器 */ + (void)dismissTwiceViewControllerAnimated: ( BOOL )flag completion: (void (^ __nullable)(void))completion; /** dismiss掉times层控制器 */ + (void)dismissViewControllerWithTimes:( NSUInteger )times animated: ( BOOL )flag completion: (void (^ __nullable)(void))completion; /** dismiss到根层控制器 */ + (void)dismissToRootViewControllerAnimated: ( BOOL )flag completion: (void (^ __nullable)(void))completion; |
5.参数的接收,以及其它方法 在3中如果在自定义了URL后面拼接了参数,或者用字典传递了参数,那么在目的控制器怎么接收呢?其实参数的接收很简单.只要导入这个分类#import "UIViewController+DCURLRouter.h" 就行了,然后就能拿到这三个参数.
1 2 3 | NSLog (@ "接收的参数%@" , self .params); NSLog (@ "拿到URL:%@" , self .originUrl); NSLog (@ "URL路径:%@" , self .path); |
但有时我们我需要把值传递给发送push或者modal方的控制器,也就是逆传,也很简单,可以用代理或者block.有方法可以拿到当前的控制器,以及导航控制器
1 2 3 | // 拿到当前控制器 UIViewController *currentController = [ DCURLRouter sharedDCURLRouter].currentViewController; // 拿到当前控制器的导航控制器 UINavigationController *currentNavgationController = [ DCURLRouter sharedDCURLRouter].currentNavigationViewController; |
至此怎么使用就说完了,不知道感觉怎样呢? 3.DCURLRouter自定义URL跳转的的实现原理. 1.文件结构 首先看一下几个文件分别是干什么用的?
-
DCURLRouter是个单例,是主要类,所有对外的接口都是由它提供.我们就是用它通过调用类方法来实现自定义URL跳转的. -
DCURLNavgation也是单例,主要是用来重写和自定义系统的跳转方法. -
UIViewController+DCURLRouter 是UIViewController的分类,用于接收控制器的参数,以及用来创建控制器的. -
DCSingleton 单例的宏 只要在需要创建单例的类中分别导入.h文件中DCSingletonH(类名) .m文件中DCSingletonM(类名) ,这样就可以很方便的创建单例了.具体看代码. -
DCURLRouter.plist 就是用来存放与自定义URL对应的控制器名称的.
2.一个自定义URL字符串的push原理 1.跳转前我们需要为自定义的URL,设置一个对应的控制器.然后在对应的控制器中执行push操作,就能够push到对应的控制器了.
1 | [ DCURLRouter pushURLString:@ "dariel://threeitem" animated: YES ]; |
2.执行完上面一句代码,经过一些简单处理,最后会来到这里.#import "UIViewController+DCURLRouter.h" 的这个方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | + ( UIViewController *)initFromURL:( NSURL *)url withQuery:( NSDictionary *)query fromConfig:( NSDictionary *)configDict { UIViewController * VC = nil ; NSString *home; if (url.path == nil ){ // 处理url,去掉有可能会拼接的参数 home = [ NSString stringWithFormat:@ "%@://%@" , url.scheme, url.host]; } else { home = [ NSString stringWithFormat:@ "%@://%@%@" , url.scheme, url.host,url.path]; } if ([configDict.allKeys containsObject:url.scheme]){ // 字典中的所有的key是否包含传入的协议头 id config = [configDict objectForKey:url.scheme]; // 根据协议头取出值 Class class = nil ; if ([config isKindOfClass:[ NSString class ]]){ //当协议头是http https的情况 class = NSClassFromString (config); } else if ([config isKindOfClass:[ NSDictionary class ]]){ // 自定义的url情况 NSDictionary *dict = ( NSDictionary *)config; if ([dict.allKeys containsObject:home]){ class = NSClassFromString ([dict objectForKey:home]); // 根据key拿到对应的控制器名称 } } if ( class != nil ){ VC = [[ class alloc] init ]; if ([ VC respondsToSelector: @selector (open:withQuery:)]){ [ VC open:url withQuery:query]; } } // 处理网络地址的情况 if ([url.scheme isEqualToString:@ "http" ] || [url.scheme isEqualToString:@ "https" ]) { class = NSClassFromString ([configDict objectForKey:url.scheme]); VC .params = @{@ "urlStr" : [url absoluteString]}; } } return VC ; } |
在这个方法中将自定义URL创建成对应的控制器.具体啥的写的很明白了,就不详细说了啊! 3.传参的接收 注意到上面的[VC open:url withQuery:query]; 吗?是在下面这个方法中完成赋值的,但我们都有个常识,怎么在分类中保存属性呢?
1 2 3 4 5 | - (void)open:( NSURL *)url withQuery:( NSDictionary *)query{ self .path = [url path]; self .originUrl = url; if (query) { // 如果自定义url后面有拼接参数,而且又通过query传入了参数,那么优先query传入了参数 self .params = query; } else { self .params = [ self paramsURL:url]; } } |
答案是利用runtime ,runtime 可以为我们做好这个.
1 2 3 4 5 6 7 | - (void)setOriginUrl:( NSURL *)originUrl { // 为分类设置属性值 objc_setAssociatedObject( self , & URLoriginUrl , originUrl, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); } - ( NSURL *)originUrl { // 获取分类的属性值 return objc_getAssociatedObject( self , & URLoriginUrl ); } |
4.在DCURLRouter 方法中我们可以拿到在2中返回的VC,然后我们需要到DCURLNavgation中调用push方法了
1 2 3 | + (void)pushURLString:( NSString *)urlString animated:( BOOL )animated { UIViewController *viewController = [ UIViewController initFromString:urlString fromConfig:[ DCURLRouter sharedDCURLRouter].configDict]; [ DCURLNavgation pushViewController:viewController animated:animated replace: NO ]; } |
5.DCURLNavgation中怎样去处理push
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | + (void)pushViewController:( UIViewController *)viewController animated:( BOOL )animated replace:( BOOL )replace { if (!viewController) { NSAssert (0, @ "请添加与url相匹配的控制器到plist文件中,或者协议头可能写错了!" ); } else { if ([viewController isKindOfClass:[ UINavigationController class ]]) { [ DCURLNavgation setRootViewController:viewController]; } // 如果是导航控制器直接设置为根控制器 else { UINavigationController *navigationController = [ DCURLNavgation sharedDCURLNavgation].currentNavigationViewController; if (navigationController) { // 导航控制器存在 // In case it should replace, look for the last UIViewController on the UINavigationController, if it's of the same class, replace it with a new one. if (replace && [navigationController.viewControllers.lastObject isKindOfClass:[viewController class ]]) { NSArray *viewControllers = [navigationController.viewControllers subarrayWithRange: NSMakeRange (0, navigationController.viewControllers.count-1)]; [navigationController setViewControllers:[viewControllers arrayByAddingObject:viewController] animated:animated]; } // 切换当前导航控制器 需要把原来的子控制器都取出来重新添加 else { [navigationController pushViewController:viewController animated:animated]; } // 进行push } else { navigationController = [[ UINavigationController alloc]initWithRootViewController:viewController]; [ DCURLNavgation sharedDCURLNavgation].applicationDelegate.window.rootViewController = navigationController; } // 如果导航控制器不存在,就会创建一个新的,设置为根控制器 } } } |
代码写的很详细,就不详细说了啊! 6.大概同理,DCURLNavgation中怎样去处理modal
1 2 3 4 5 6 7 8 9 | + (void)presentViewController:( UIViewController *)viewController animated: ( BOOL )flag completion:(void (^ __nullable)(void))completion { if (!viewController) { NSAssert (0, @ "请添加与url相匹配的控制器到plist文件中,或者协议头可能写错了!" ); } else { UIViewController *currentViewController = [[ DCURLNavgation sharedDCURLNavgation] currentViewController]; if (currentViewController) { // 当前控制器存在 [currentViewController presentViewController:viewController animated:flag completion:completion]; } else { // 将控制器设置为根控制器 [ DCURLNavgation sharedDCURLNavgation].applicationDelegate.window.rootViewController = viewController; } } } |
代码也很详细,有问题可以在下面留言! 4. 怎样去加载一个自定义的webView控制器 在上面3.2.2中,不知道有没有注意到那个对网络地址的处理
1 2 3 | // 处理网络地址的情况 if ([url.scheme isEqualToString:@"http"] || [url.scheme isEqualToString:@"https"]) { class = NSClassFromString ([configDict objectForKey:url.scheme]); VC .params = @{@ "urlStr" : [url absoluteString]}; |
如果协议头是http或者https的情况,我们可以通过[configDict objectForKey:url.scheme]拿到自定义webView控制器的名称,然后再去创建webView控制器,之后我们是将url通过参数传到webView控制器中,最后在webView控制器中加载对应的webview. 5.关于怎样一次性pop和dismiss多层控制器的实现原理. 1.pop控制器
1 2 3 4 5 6 7 8 | + (void)popViewControllerWithTimes:( NSUInteger )times animated:( BOOL )animated { UIViewController *currentViewController = [[ DCURLNavgation sharedDCURLNavgation] currentViewController]; NSUInteger count = currentViewController.navigationController.viewControllers.count; if (currentViewController){ if (currentViewController.navigationController) { if (count > times){ [currentViewController.navigationController popToViewController:[currentViewController.navigationController.viewControllers objectAtIndex:count-1-times] animated:animated]; } else { // 如果times大于控制器的数量 NSAssert (0, @ "确定可以pop掉那么多控制器?" ); } } } } |
popViewController 实现的思路比较简单,因为可以拿到导航控制器上的所有控制器,然后通过objectAtIndex 这个方法.这样就能做到了. 2.dismiss控制器
1 2 3 4 5 6 7 8 | + (void)dismissViewControllerWithTimes:( NSUInteger )times animated: ( BOOL )flag completion: (void (^ __nullable)(void))completion { UIViewController *rootVC = [[ DCURLNavgation sharedDCURLNavgation] currentViewController]; if (rootVC) { while (times > 0) { rootVC = rootVC.presentingViewController; times -= 1; } [rootVC dismissViewControllerAnimated: YES completion:completion]; } if (!rootVC.presentedViewController) { NSAssert (0, @ "确定能dismiss掉这么多控制器?" ); } } |
dismissViewController这个的实现思路就有点特别了,因为没有办法拿到所有的modal出来的控制器,只能拿到上一个,所以这边就是用的while循环实现的. 5.总结 大概讲了下具体的使用和大概功能的实现,还有很多具体实现细节,有兴趣的童鞋可以看给出的源码! DCURLRouter组件源码: https://github.com/DarielChen/DCURLRouter 欢迎使用,欢迎star,你的star就是对我最好的鼓励. 原文:http://bbs.520it.com/forum.php?mod=viewthread&tid=2746 |