iOS使用自定義URL實(shí)現(xiàn)控制器之間的跳轉(zhuǎn)

一個(gè)app往往有很多界面,而界面之間的跳轉(zhuǎn)也就是對(duì)應(yīng)控制器的跳轉(zhuǎn),控制器的跳轉(zhuǎn)一般有兩種情況 push 或者 modal,push 和 modal 的默認(rèn)效果是系統(tǒng)提供的,但也可以自定義.有興趣了解一下自定義的童鞋可以看這篇,iOS動(dòng)畫指南 - 6.可以很酷的轉(zhuǎn)場動(dòng)畫.

文章配圖

1. 概述

系統(tǒng)提供的push和modal方法有時(shí)并不能滿足實(shí)際需求.比如,我們需要根據(jù)服務(wù)器返回的字段跳到指定的控制器,難道作判斷嗎?那顯然不是最佳解決方案.

其實(shí)我們可以這樣:

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

對(duì)的,就是通過自定義URL+拼接參數(shù),實(shí)現(xiàn)跳轉(zhuǎn).當(dāng)然啦,DCURLRouter的功能遠(yuǎn)不止這點(diǎn).

2.DCURLRouter的基本使用

DCURLRouter是一個(gè)通過簡單配置就能夠?qū)崿F(xiàn)自定義URL跳轉(zhuǎn)的開源組件: GitHub
你的star是對(duì)我最好的支持.??

1.簡單集成

只要把DCURLRouter這個(gè)文件夾拖到項(xiàng)目中就行了,或者也可以使用cocoapods.

2. 簡單配置

  1. 每一個(gè)自定義的URL都會(huì)有一個(gè)對(duì)應(yīng)的控制器,那Xocde怎么知道呢?我們需要一個(gè)plist文件.打開DCURLRouter.plist文件

    內(nèi)部結(jié)構(gòu)大概長這樣.除了自定義的URL上面還有httphttps,這是當(dāng)如果URL是網(wǎng)頁鏈接的時(shí)候,DCURLRouter會(huì)自動(dòng)跳轉(zhuǎn)到自定義好的webView控制器,并把URL當(dāng)成參數(shù)傳遞到webView控制器.是不是很方便. 下面的dariel字典就是用來存放自定義URL以及對(duì)應(yīng)的控制器名稱的.dariel就是自定義協(xié)議頭了.以后就可以把自定義的URL和對(duì)應(yīng)的控制器放這里了.
  2. 加載DCURLRouter.plist文件數(shù)據(jù)
- (BOOL)application:(UIApplication *)application  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
       [DCURLRouter loadConfigDictFromPlist:@"DCURLRouter.plist"];
       return YES;
}

3. push和modal的使用

所有的push和modal方法都可以通過DCURLRouter這個(gè)類方法來調(diào)用.這樣在push和modal的時(shí)候就不需要拿到導(dǎo)航控制器或控制器再跳轉(zhuǎn)了.也就是說,以后push和modal控制器跳轉(zhuǎn)就不一定要在控制器中進(jìn)行了.

  1. push控制器
    // 不需要拼接參數(shù)直接跳轉(zhuǎn)
    [DCURLRouter pushURLString:@"dariel://twoitem" animated:YES];
    
    // 直接把參數(shù)拼接在自定義url末尾
    NSString *urlStr = @"dariel://twoitem?name=dariel&userid=213213";
    [DCURLRouter pushURLString:urlStr animated:YES];
    // 可以將參數(shù)放入一個(gè)字典
    NSDictionary *dict = @{@"userName":@"Hello", @"userid":@"32342"};
    [DCURLRouter pushURLString:@"dariel://twoitem" query:dict animated:YES];

    // 如果當(dāng)前控制器和要push的控制器是同一個(gè),可以將replace設(shè)置為Yes,進(jìn)行替換.
    [DCURLRouter pushURLString:@"dariel://oneitem" query:dict animated:YES replace:YES];
    
    // 重寫了系統(tǒng)的push方法,直接通過控制器跳轉(zhuǎn)
    TwoViewController *two = [[TwoViewController alloc] init];
    [DCURLRouter pushViewController:two animated:YES];
  1. modal控制器
    用法和push差不多,只是這里添加了一個(gè)給modal出來的控制器加一個(gè)導(dǎo)航控制器的方法.
   // 不需要拼接參數(shù)直接跳轉(zhuǎn)
   [DCURLRouter presentURLString:@"dariel://threeitem" animated:YES completion:nil];
   
   // 直接把參數(shù)拼接在自定義url末尾
   NSString *urlStr = @"dariel://threeitem?name=dariel&userid=213213";
   [DCURLRouter presentURLString:urlStr animated:YES completion:nil];

   // 可以將參數(shù)放入一個(gè)字典
   NSDictionary *dict = @{@"userName":@"Hello", @"userid":@"32342"};
   [DCURLRouter presentURLString:@"dariel://threeitem" query:dict animated:YES completion:nil];

   // 給modal出來的控制器添加一個(gè)導(dǎo)航控制器
   [DCURLRouter presentURLString:@"dariel://threeitem" animated:YES withNavigationClass:[UINavigationController class] completion:nil];

   // 重寫了系統(tǒng)的push方法
   ThreeViewController *three = [[ThreeViewController alloc] init];
   [DCURLRouter presentViewController:three animated:YES completion:nil];

4. 后退 pop 和 dismiss

在實(shí)際開發(fā)中,好幾次的界面的跳轉(zhuǎn)組成了一個(gè)業(yè)務(wù)流程,整個(gè)業(yè)務(wù)流程結(jié)束后通常會(huì)要求返回最開始的界面,這就要讓控制器連續(xù)后退好幾次,但蘋果是沒有提供方法的.DCURLRouter給出了具體的實(shí)現(xiàn)方案.
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.參數(shù)的接收,以及其它方法

在3中如果在自定義了URL后面拼接了參數(shù),或者用字典傳遞了參數(shù),那么在目的控制器怎么接收呢?其實(shí)參數(shù)的接收很簡單.只要導(dǎo)入這個(gè)分類#import "UIViewController+DCURLRouter.h"就行了,然后就能拿到這三個(gè)參數(shù).

    NSLog(@"接收的參數(shù)%@", self.params);
    NSLog(@"拿到URL:%@", self.originUrl);
    NSLog(@"URL路徑:%@", self.path);

但有時(shí)我們我需要把值傳遞給發(fā)送push或者modal方的控制器,也就是逆?zhèn)?也很簡單,可以用代理或者block.有方法可以拿到當(dāng)前的控制器,以及導(dǎo)航控制器

  
    // 拿到當(dāng)前控制器
    UIViewController *currentController = [DCURLRouter sharedDCURLRouter].currentViewController;
    // 拿到當(dāng)前控制器的導(dǎo)航控制器
    UINavigationController *currentNavgationController = [DCURLRouter sharedDCURLRouter].currentNavigationViewController;

至此怎么使用就說完了,不知道感覺怎樣呢?

3.DCURLRouter自定義URL跳轉(zhuǎn)的的實(shí)現(xiàn)原理.

1.文件結(jié)構(gòu)

首先看一下幾個(gè)文件分別是干什么用的?


  • DCURLRouter是個(gè)單例,是主要類,所有對(duì)外的接口都是由它提供.我們就是用它通過調(diào)用類方法來實(shí)現(xiàn)自定義URL跳轉(zhuǎn)的.
  • DCURLNavgation也是單例,主要是用來重寫和自定義系統(tǒng)的跳轉(zhuǎn)方法.
  • UIViewController+DCURLRouter 是UIViewController的分類,用于接收控制器的參數(shù),以及用來創(chuàng)建控制器的.
  • DCSingleton 單例的宏 只要在需要?jiǎng)?chuàng)建單例的類中分別導(dǎo)入.h文件中DCSingletonH(類名) .m文件中DCSingletonM(類名) ,這樣就可以很方便的創(chuàng)建單例了.具體看代碼.
  • DCURLRouter.plist 就是用來存放與自定義URL對(duì)應(yīng)的控制器名稱的.

2.一個(gè)自定義URL字符串的push原理

  1. 跳轉(zhuǎn)前我們需要為自定義的URL,設(shè)置一個(gè)對(duì)應(yīng)的控制器.然后在對(duì)應(yīng)的控制器中執(zhí)行push操作,就能夠push到對(duì)應(yīng)的控制器了.


    [DCURLRouter pushURLString:@"dariel://threeitem" animated:YES];
  1. 執(zhí)行完上面一句代碼,經(jīng)過一些簡單處理,最后會(huì)來到這里.#import "UIViewController+DCURLRouter.h"的這個(gè)方法中
+ (UIViewController *)initFromURL:(NSURL *)url withQuery:(NSDictionary *)query fromConfig:(NSDictionary *)configDict
{
   UIViewController *VC = nil;
   NSString *home;
   if(url.path == nil){ // 處理url,去掉有可能會(huì)拼接的參數(shù)
       home = [NSString stringWithFormat:@"%@://%@", url.scheme, url.host];
   }else{
       home = [NSString stringWithFormat:@"%@://%@%@", url.scheme, url.host,url.path];
   }
   if([configDict.allKeys containsObject:url.scheme]){ // 字典中的所有的key是否包含傳入的協(xié)議頭
       id config = [configDict objectForKey:url.scheme]; // 根據(jù)協(xié)議頭取出值
       Class class = nil;
       if([config isKindOfClass:[NSString class]]){ //當(dāng)協(xié)議頭是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]); // 根據(jù)key拿到對(duì)應(yīng)的控制器名稱
           }
       }
       if(class !=nil){
           VC = [[class alloc]init];
           if([VC respondsToSelector:@selector(open:withQuery:)]){
               [VC open:url withQuery:query];
           }
       }
       // 處理網(wǎng)絡(luò)地址的情況
       if ([url.scheme isEqualToString:@"http"] || [url.scheme isEqualToString:@"https"]) {
           class =  NSClassFromString([configDict objectForKey:url.scheme]);
           VC.params = @{@"urlStr": [url absoluteString]};
       }
   }
   return VC;
}

在這個(gè)方法中將自定義URL創(chuàng)建成對(duì)應(yīng)的控制器.具體啥的寫的很明白了,就不詳細(xì)說了啊!

  1. 傳參的接收
    注意到上面的[VC open:url withQuery:query];嗎?是在下面這個(gè)方法中完成賦值的,但我們都有個(gè)常識(shí),怎么在分類中保存屬性呢?
- (void)open:(NSURL *)url withQuery:(NSDictionary *)query{
   self.path = [url path];
   self.originUrl = url;
   if (query) {   // 如果自定義url后面有拼接參數(shù),而且又通過query傳入了參數(shù),那么優(yōu)先query傳入了參數(shù)
       self.params = query;
   }else {
       self.params = [self paramsURL:url];
   }
}

答案是利用runtime,runtime可以為我們做好這個(gè).

- (void)setOriginUrl:(NSURL *)originUrl {
    // 為分類設(shè)置屬性值
   objc_setAssociatedObject(self, &URLoriginUrl,
                            originUrl,
                            OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSURL *)originUrl {
   // 獲取分類的屬性值
   return objc_getAssociatedObject(self, &URLoriginUrl);
}
  1. DCURLRouter方法中我們可以拿到在2中返回的VC,然后我們需要到DCURLNavgation中調(diào)用push方法了
+ (void)pushURLString:(NSString *)urlString animated:(BOOL)animated {
   UIViewController *viewController = [UIViewController initFromString:urlString fromConfig:[DCURLRouter sharedDCURLRouter].configDict];
   [DCURLNavgation pushViewController:viewController animated:animated replace:NO];
}
  1. DCURLNavgation中怎樣去處理push
 + (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated replace:(BOOL)replace
 {
       if (!viewController) {
        NSAssert(0, @"請(qǐng)?zhí)砑优curl相匹配的控制器到plist文件中,或者協(xié)議頭可能寫錯(cuò)了!");
   }
   else {
       if([viewController isKindOfClass:[UINavigationController class]]) {
           [DCURLNavgation setRootViewController:viewController];
       } // 如果是導(dǎo)航控制器直接設(shè)置為根控制器
       else {
           UINavigationController *navigationController = [DCURLNavgation sharedDCURLNavgation].currentNavigationViewController;
           if (navigationController) { // 導(dǎo)航控制器存在
               // 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];
               } // 切換當(dāng)前導(dǎo)航控制器 需要把原來的子控制器都取出來重新添加
               else {
                   [navigationController pushViewController:viewController animated:animated];
               } // 進(jìn)行push
           }
           else {
               navigationController = [[UINavigationController alloc]initWithRootViewController:viewController];
               [DCURLNavgation sharedDCURLNavgation].applicationDelegate.window.rootViewController = navigationController;
           } // 如果導(dǎo)航控制器不存在,就會(huì)創(chuàng)建一個(gè)新的,設(shè)置為根控制器
       }
   }
 }

代碼寫的很詳細(xì),就不詳細(xì)說了啊!

  1. 大概同理,DCURLNavgation中怎樣去處理modal
 + (void)presentViewController:(UIViewController *)viewController animated: (BOOL)flag completion:(void (^ __nullable)(void))completion
{
    if (!viewController) {
         NSAssert(0, @"請(qǐng)?zhí)砑优curl相匹配的控制器到plist文件中,或者協(xié)議頭可能寫錯(cuò)了!");
    }else {
        UIViewController *currentViewController = [[DCURLNavgation sharedDCURLNavgation] currentViewController];
        if (currentViewController) { // 當(dāng)前控制器存在
            [currentViewController presentViewController:viewController animated:flag completion:completion];
        } else { // 將控制器設(shè)置為根控制器
            [DCURLNavgation sharedDCURLNavgation].applicationDelegate.window.rootViewController = viewController;
        }
    }
}

代碼也很詳細(xì),有問題可以在下面留言!

4. 怎樣去加載一個(gè)自定義的webView控制器

在上面3.2.2中,不知道有沒有注意到那個(gè)對(duì)網(wǎng)絡(luò)地址的處理

// 處理網(wǎng)絡(luò)地址的情況 
if ([url.scheme isEqualToString:@"http"] || [url.scheme isEqualToString:@"https"]) { 
class = NSClassFromString([configDict objectForKey:url.scheme]); 
VC.params = @{@"urlStr": [url absoluteString]};

如果協(xié)議頭是http或者h(yuǎn)ttps的情況,我們可以通過[configDict objectForKey:url.scheme]拿到自定義webView控制器的名稱,然后再去創(chuàng)建webView控制器,之后我們是將url通過參數(shù)傳到webView控制器中,最后在webView控制器中加載對(duì)應(yīng)的webview.

5.關(guān)于怎樣一次性pop和dismiss多層控制器的實(shí)現(xiàn)原理.

  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大于控制器的數(shù)量
               NSAssert(0, @"確定可以pop掉那么多控制器?");
           }
       }
   }
}

popViewController實(shí)現(xiàn)的思路比較簡單,因?yàn)榭梢阅玫綄?dǎo)航控制器上的所有控制器,然后通過objectAtIndex這個(gè)方法.這樣就能做到了.

  1. dismiss控制器
+ (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這個(gè)的實(shí)現(xiàn)思路就有點(diǎn)特別了,因?yàn)闆]有辦法拿到所有的modal出來的控制器,只能拿到上一個(gè),所以這邊就是用的while循環(huán)實(shí)現(xiàn)的.

5.總結(jié)

大概講了下具體的使用和大概功能的實(shí)現(xiàn),還有很多具體實(shí)現(xiàn)細(xì)節(jié),有興趣的童鞋可以看給出的源碼!
DCURLRouter組件源碼: https://github.com/DarielChen/DCURLRouter
歡迎使用,歡迎star,你的star就是對(duì)我最好的鼓勵(lì).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容