[iOS] iOS 使用自定義 URL 實現控制器之間的跳轉

ColemanVerg 8年前發布 | 31K 次閱讀 URL iOS開發 移動開發

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

文章配圖

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 的功能遠不止這點.

2.DCURLRouter的基本使用

DCURLRouter是一個通過簡單配置就能夠實現自定義URL跳轉的開源組件: GitHub

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

歡迎使用,歡迎star,你的star就是對我最好的鼓勵.

 

來自:http://www.jianshu.com/p/36a43202b0cd

 

Save

 本文由用戶 ColemanVerg 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!