使用自定义 URL 实现控制器之间的跳转-b

一个app往往有很多界面,而界面之间的跳转也就是对应控制器的跳转,控制器的跳转一般有两种情况 push 或者 modal,push 和 modal 的默认效果是系统提供的

文章配图

1. 概述

系统提供的push和modal方法有时并不能满足实际需求.比如,我们需要根据服务器返回的字段跳到指定的控制器,难道作判断吗?那显然不是最佳解决方案.

其实我们可以这样:

NSString *urlStr = @"dariel://twoitem?name=dariel&userid=213213";      
// push    
[DCURLRouter pushURLString:urlStr animated:YES];      
// modal    
[DCURLRouter presentURLString:urlStr animated:YES completion:nil];

对的,就是通过自定义URL+拼接参数,实现跳转.当然啦, DCURLRouter :https://github.com/DarielChen/DCURLRouter 的功能远不止这点.

2.DCURLRouter的基本使用

ps.DCURLRouter是OC版的,后续看情况可能会有swift版本的.

你的star是对我最好的支持.:smiley:

1.简单集成

只要把 DCURLRouter 这个文件夹拖到项目中就行了,后续会支持 cocoapods .

2. 简单配置

  1. 每一个自定义的URL都会有一个对应的控制器,那Xocde怎么知道呢?我们需要一个plist文件.打开 DCURLRouter.plist 文件

    内部结构大概长这样.除了自定义的URL上面还有 http 和 https ,这是当如果URL是网页链接的时候,DCURLRouter会自动跳转到自定义好的 webView控制器 ,并把URL当成参数传递到webView控制器.是不是很方便. 下面的 dariel 字典就是用来存放自定义URL以及对应的控制器名称的. dariel 就是自定义协议头了.以后就可以把自定义的URL和对应的控制器放这里了.

  2. 加载DCURLRouter.plist文件数据
- (BOOL)application:(UIApplication *)application  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    [DCURLRouter loadConfigDictFromPlist:@"DCURLRouter.plist"];    
    return YES;
}

3. push和modal的使用

所有的push和modal方法都可以通过DCURLRouter这个类方法来调用.这样在push和modal的时候就不需要拿到导航控制器或控制器再跳转了.也就是说,以后push和modal控制器跳转就不一定要在控制器中进行了.

  1. push控制器

    // 不需要拼接参数直接跳转
    [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];
  2. modal控制器

    用法和push差不多,只是这里添加了一个给modal出来的控制器加一个导航控制器的方法.

    // 不需要拼接参数直接跳转
    [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:

/** 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:

/** 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" 就行了,然后就能拿到这三个参数.

NSLog(@"接收的参数%@", self.params);    
NSLog(@"拿到URL:%@", self.originUrl);    
NSLog(@"URL路径:%@", self.path);

但有时我们我需要把值传递给发送push或者modal方的控制器,也就是逆传,也很简单,可以用代理或者block.有方法可以拿到当前的控制器,以及导航控制器

// 拿到当前控制器    
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到对应的控制器了.

    [DCURLRouter pushURLString:@"dariel://threeitem" animated:YES];
  2. 执行完上面一句代码,经过一些简单处理,最后会来到这里. #import "UIViewController+DCURLRouter.h" 的这个方法中
    + (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]; 吗?是在下面这个方法中完成赋值的,但我们都有个常识,怎么在分类中保存属性呢?

    - (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 可以为我们做好这个.

    - (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方法了
    + (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
    + (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
    + (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中,不知道有没有注意到那个对网络地址的处理

// 处理网络地址的情况  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控制器

    + (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控制器
    + (void)dismissViewControllerWithTimes:(NSUInteger)times animated: (BOOL)flag completion: (void (^ __nullable)(void))completion {
        UIViewController *rootVC = [[DCURLNavgation sharedDCURLNavgation] currentViewController];
        if (rootVC.presentedViewController) {      
            while (times > 0) {          
                rootVC = rootVC.presentingViewController;          
                times -= 1;      
            }      
            [rootVC dismissViewControllerAnimated:YES completion:completion]; 
    
        }else {      
            NSAssert(0, @"确定能dismiss掉这么多控制器?");
        }
    }

    dismissViewController这个的实现思路就有点特别了,因为没有办法拿到所有的modal出来的控制器,只能拿到上一个,所以这边就是用的while循环实现的.

5.总结

大概讲了下具体的使用和大概功能的实现,还有很多具体实现细节,有兴趣的童鞋可以看给出的源码!

DCURLRouter组件源码: https://github.com/DarielChen/DCURLRouter

时间: 2024-11-05 11:46:42

使用自定义 URL 实现控制器之间的跳转-b的相关文章

多控制器之间的跳转

1>  什么是多控制器之间的跳转? 在一个 APP 中,会存在多个控制器,多个控制器的存在就会存在控制器的跳转问题. 2>  包含:连线跳转,modal 方式以及navigationController/tabBarontroller方式 连线跳转:根据绑定的 ID 进行控制器的跳转 连线跳转的分类: > 自动型:点击控件后,自动跳转到下一个控制器(action – push 方式) > 手动型:需要借助于代码手动完成. 手动型,是指从来源控制器拖到目标控制器.(manual –

【iOS开发-79】利用Modal方式实现控制器之间的跳转

利用Modal方法.事实上就是以下两个方法的运用. Modal方式的切换效果是从底部呈现. -(void)clickModal{ WPViewController *wp=[[WPViewController alloc]init]; [self presentViewController:wp animated:YES completion:^{ }]; } -(void)clickDismiss{ [self dismissViewControllerAnimated:YES complet

IOS开发UI篇-NavigationController的控制器之间的跳转

一.效果图如下 1> 第一个控制器的NavigationBar隐藏 2> 有按钮,可以跳转到下一个控制器,返回上一个控制器或者根控制器 二.思路代码 思路: 1> 设置window的跟控制器为navigationController 2> 设置一个控制器的基类 主要代码实现如下: AppDelegate.m // // AppDelegate.m // navigation的简单实用(纯代码) // // Created by gxiangzi on 15/8/7. // Copy

控制器之间的跳转

1.代码跳转,vc的界面需要写在xib上 UIViewController *vc = [[UIViewController alloc] init];//普通控制器 [self presentViewController:vc animated:YES completion:nil]; 2.storyboard按钮跳转,无连线,也可以传值- (IBAction)itemClick:(id)sender {    UIViewController *three = [[UIStoryboard

控制器之间的跳转,多层的跳转

UIViewController *target = nil;for (UIViewController * controller in self.navigationController.viewControllers) { //遍历 if ([controller isKindOfClass:[SecretGardenController class]]) { //这里判断是否为你想要跳转的页面 target = controller; }}if (target) { [self.navig

php--tp中页面之间的跳转

__MODULE__   不同控制器之间进行跳转 __APP__  /  __URL__  用的比较多

ThinkPHP教程_PHP框架之ThinkPHP(二)【URL路径访问与模块控制器、URL四种模式、PATHINFO的两种模式、模板与控制器之间的关系】

一.URL路径访问与模块控制器 URL 模块(控制器) 动作(方法) 即以上三者之间的关系URL:http://127.0.0.1/projectName/index.php/模块/动作 1.ThinkPHP规定,两点 ·第一.所有的主入口文件默认访问index控制器(模块) ·第二.所有的控制器默认执行index方法(动作) 特别强调一下,以上两点是独立的!也就是说"所有的主入口文件默认访问index控制器,并执行默认执行index方法"是不准确的 那么,http://127.0.0

vim matchit 自定义配对关键字之间的跳转

vim因其强大的扩展性一直深受linux程序员的喜爱,最近在用vim写verilog的时候,由于一个逻辑块中的begin end较多,常常会多写或者漏掉匹配关键字,很是苦恼,于是寻找匹配关键字间跳转的方法,发现matchit可以用%在关键字之间实现跳转,还可以自定义更多的关键字,相当方便. 以下正片: 1.安装 matchit插件 matchit 插件是vim默认自带的,但是不安装.安装方法可以在vim 的help里查看: $ vi     打开一个空白文件 :help matchit-inst

UINavigation的外观设置和页面之间的跳转

创建导航: 1.创建一个视图控制器的类a 2.在app中引用 3.在app.m中创建a对象 4.创建一个导航视图,并且把a对象作为导航视图的根视图 5.设置window的根视图为导航视图控制器,代码: 6.到a.m文件中给导航设置属性,如:(给导航设置属性大部分都是.navigationItem) 用导航的两个视图之间的跳转: 1.再创建一个视图控制器,在第一个视图控制器还有第二个视图控制器上面都添加一个按钮,来用于页面之间的跳转 2.第一个页面到第二个页面之间的跳转button的方法,代码:(