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

原文出處:Dariel在杭州 php

一個app每每有不少界面,而界面之間的跳轉也就是對應控制器的跳轉,控制器的跳轉通常有兩種狀況 push 或者 modal,push 和 modal 的默認效果是系統提供的,但也能夠自定義.有興趣瞭解一下自定義的童鞋能夠看這篇,iOS動畫指南 - 6.能夠很酷的轉場動畫.git

文章配圖github

1. 概述

系統提供的push和modal方法有時並不能知足實際需求.好比,咱們須要根據服務器返回的字段跳到指定的控制器,難道做判斷嗎?那顯然不是最佳解決方案.web

其實咱們能夠這樣:swift

1服務器

2網絡

3app

4動畫

5url

    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. 簡單配置

  1. 每個自定義的URL都會有一個對應的控制器,那Xocde怎麼知道呢?咱們須要一個plist文件.打開DCURLRouter.plist文件


    內部結構大概長這樣.除了自定義的URL上面還有httphttps,這是當若是URL是網頁連接的時候,DCURLRouter會自動跳轉到自定義好的webView控制器,並把URL當成參數傳遞到webView控制器.是否是很方便. 下面的dariel字典就是用來存放自定義URL以及對應的控制器名稱的.dariel就是自定義協議頭了.之後就能夠把自定義的URL和對應的控制器放這裏了.

  2. 加載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 nilNSString *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

相關文章
相關標籤/搜索