原文出處: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. 簡單配置
-
每個自定義的URL都會有一個對應的控制器,那Xocde怎麼知道呢?咱們須要一個plist文件.打開DCURLRouter.plist 文件 ![](http://static.javashuo.com/static/loading.gif) 內部結構大概長這樣.除了自定義的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.文件結構 首先看一下幾個文件分別是幹什麼用的? ![](http://static.javashuo.com/static/loading.gif)
-
DCURLRouter是個單例,是主要類,全部對外的接口都是由它提供.咱們就是用它經過調用類方法來實現自定義URL跳轉的.
-
DCURLNavgation也是單例,主要是用來重寫和自定義系統的跳轉方法.
-
UIViewController+DCURLRouter 是UIViewController的分類,用於接收控制器的參數,以及用來建立控制器的.
-
DCSingleton 單例的宏 只要在須要建立單例的類中分別導入.h文件中DCSingletonH(類名) .m文件中DCSingletonM(類名) ,這樣就能夠很方便的建立單例了.具體看代碼.
-
DCURLRouter.plist 就是用來存放與自定義URL對應的控制器名稱的.
2.一個自定義URL字符串的push原理 1.跳轉前咱們須要爲自定義的URL,設置一個對應的控制器.而後在對應的控制器中執行push操做,就可以push到對應的控制器了. ![](http://static.javashuo.com/static/loading.gif)
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 |