一個app往往有很多介面,而介面之間的跳轉也就是對應控制器的跳轉,控制器的跳轉一般有兩種情況 push 或者 modal,push 和 modal 的預設效果是系統提供的,但也可以自定義.有興趣瞭解一下自定義的童鞋可以看這篇,iOS動畫指南 – 6.可以很酷的轉場動畫.
1. 概述
系統提供的push和modal方法有時並不能滿足實際需求.比如,我們需要根據伺服器返回的欄位跳到指定的控制器,難道作判斷嗎?那顯然不是最佳解決方案.
其實我們可以這樣:
1 2 3 4 5 |
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
檔案內部結構大概長這樣.除了自定義的URL上面還有
http
和https
,這是當如果URL是網頁連結的時候,DCURLRouter會自動跳轉到自定義好的webView控制器
,並把URL當成引數傳遞到webView控制器.是不是很方便. 下面的dariel
字典就是用來存放自定義URL以及對應的控制器名稱的.dariel
就是自定義協議頭了.以後就可以把自定義的URL和對應的控制器放這裡了. - 載入DCURLRouter.plist檔案資料
1234- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[DCURLRouter loadConfigDictFromPlist:@"DCURLRouter.plist"];return YES;}
3. push和modal的使用
所有的push和modal方法都可以通過DCURLRouter這個類方法來呼叫.這樣在push和modal的時候就不需要拿到導航控制器或控制器再跳轉了.也就是說,以後push和modal控制器跳轉就不一定要在控制器中進行了.
- push控制器
12345678910111213141516// 不需要拼接引數直接跳轉[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]; - modal控制器
用法和push差不多,只是這裡新增了一個給modal出來的控制器加一個導航控制器的方法.
1234567891011121314151617// 不需要拼接引數直接跳轉[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 6 7 8 |
/** 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 6 7 8 |
/** 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 4 |
// 拿到當前控制器 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原理
- 跳轉前我們需要為自定義的URL,設定一個對應的控制器.然後在對應的控制器中執行push操作,就能夠push到對應的控制器了.
1[DCURLRouter pushURLString:@"dariel://threeitem" animated:YES];
- 執行完上面一句程式碼,經過一些簡單處理,最後會來到這裡.
#import "UIViewController+DCURLRouter.h"
的這個方法中12345678910111213141516171819202122232425262728293031323334+ (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建立成對應的控制器.具體啥的寫的很明白了,就不詳細說了啊!
- 傳參的接收
注意到上面的[VC open:url withQuery:query];
嗎?是在下面這個方法中完成賦值的,但我們都有個常識,怎麼在分類中儲存屬性呢?123456789- (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
可以為我們做好這個.12345678910- (void)setOriginUrl:(NSURL *)originUrl {// 為分類設定屬性值objc_setAssociatedObject(self, &URLoriginUrl,originUrl,OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSURL *)originUrl {// 獲取分類的屬性值return objc_getAssociatedObject(self, &URLoriginUrl);} - 在
DCURLRouter
方法中我們可以拿到在2中返回的VC,然後我們需要到DCURLNavgation中呼叫push方法了1234+ (void)pushURLString:(NSString *)urlString animated:(BOOL)animated {UIViewController *viewController = [UIViewController initFromString:urlString fromConfig:[DCURLRouter sharedDCURLRouter].configDict];[DCURLNavgation pushViewController:viewController animated:animated replace:NO];} - DCURLNavgation中怎樣去處理push
1234567891011121314151617181920212223242526272829+ (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;} // 如果導航控制器不存在,就會建立一個新的,設定為根控制器}}}
程式碼寫的很詳細,就不詳細說了啊!
- 大概同理,DCURLNavgation中怎樣去處理modal
12345678910111213+ (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 4 |
// 處理網路地址的情況 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多層控制器的實現原理.
- pop控制器
12345678910111213+ (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
這個方法.這樣就能做到了. - dismiss控制器
12345678910111213+ (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就是對我最好的鼓勵.