我們都知道蘋果手機中的APP都有一個沙盒,APP就是一個信息孤島,相互是不可以進行通信的。但是iOS的APP可以注冊自己的URL Scheme,URL Scheme是為方便app之間互相調用而設計的。這也是scheme最常用到的地方,但是平時項目中還有另外兩個地方一樣需要用到:服務器通知客戶端如何跳轉和H5與Native跳轉。所以制定一個統一的scheme協議來完成APP間跳轉和App內頁面跳轉,然后定義一個專門的類來處理相關跳轉,可以使代碼更加整潔優雅。
scheme三方面的作用:
- 服務器下發跳轉路徑,客戶端根據服務器下發跳轉路徑跳轉相應的頁面;
- H5頁面點擊錨點,根據錨點具體跳轉路徑APP端跳轉具體的頁面;
- APP端收到服務器端下發的PUSH通知欄消息,根據消息的點擊跳轉路徑跳轉相關頁面
URL scheme 概述
客戶端應用可以向操作系統注冊一個 URL scheme,該 scheme 用于從瀏覽器或其他應用中啟動本應用。通過指定的 URL 字段,可以讓應用在被調起后直接打開某些特定頁面,比如車輛詳情頁、訂單詳情頁、消息通知頁、促銷廣告頁等等。也可以執行某些指定動作,如訂單支付等。也可以在應用內通過 html 頁來直接調用顯示 app 內的某個頁面。
URL scheme 的格式
客戶端自定義的 URL 作為從一個應用調用另一個的基礎,遵循 RFC 1808 (Relative Uniform Resource Locators) 標準。這跟我們常見的網頁內容 URL 格式一樣。
一個普通的 URL 分為幾個部分,scheme、host、relativePath、query。
我們用到的NSURL
NSURL *url = [NSURL URLWithString:@"http://www.testurl.com:8080/subpath/subsubpath?uid=123&gid=456"];
[url scheme]為http, [url host]為www.testurl.com,[url port]為8080,[url path]為/subpath/subsubpath,[url lastPathComponent]為subsubpath,[url query]為uid=123&gid=456
一個應用中使用的 URL 例子(該 URL 會調起車輛詳情頁):
zqprojectmobile://project/carDetail?car_id=123456
scheme為zqprojectmobile,host為project,relativePath為/carDetail,query為car_id=123456
項目中定義了專門的類命名為JumpURLHandle,通過類方法parseURL:來處理參數中的url。本文以此為例講解scheme的定義與解析。
1 首先客戶端應用向操作系統注冊一個或者多個 URL scheme,例如項目中就定 義了多個分別scheme,分別為:
zqprojectmobile: 對應普通APP間跳轉scheme
zqprojectwxpay: 對應微信支付完成之后跳轉回來的scheme
zqprojectalipay:對應支付寶支付完成之后跳轉回來的scheme
對應的parseURL:方法里解析為:
+ (BOOL)parseURL:(NSURL *)url
{
// 支付寶客戶端支付后的回調
if ([[url scheme] isEqualToString:@"zqprojectalipay"]
|| ([[[url scheme] lowercaseString] isEqualToString:kUuyongcheAlipayScheme]))
{
return 支付寶支付完成后的回調處理方法;
}
// 微信客戶端支付后的回調
else if ([url.scheme isEqualToString:@"zqprojectwxpay"] && [url.host isEqualToString:@"pay"])
{
return 微信支付完成后的回調處理方法;
}
// 本應用 scheme 調用
else if (([[url scheme] isEqualToString:@"zqprojectmobile"])
|| ([[[url scheme] lowercaseString] isEqualToString:@"zqprojectmobile"]))
{
return [[JumpURLHandle getInstance] parseAppUrl:url];
}
return NO;
}
2 定義relativePath,并通過relativePath來判斷是執行動作還是跳轉頁面,當執行動作時把relativePath定義為"/action",在解析時如果url的relativePath是"/action"則跳轉到執行動作的處理方法里,否則執行跳轉頁面的邏輯。
例如:
zqprojectmobile://project/action?name=back,
relativePath為/action,執行動作(返回前頁)
zqprojectmobile://project/order?order_id=42347645&type=2
relativePath為/order,執行跳轉頁面的邏輯
代碼:
- (BOOL)parseAppUrl:(NSURL *)url
{
NSString *relativePath = [url relativePath];
// scheme 調起執行動作
if ([relativePath isEqualToString:@"/action"])
{
[self jumpActions:url];
}
// scheme 調起跳轉頁面
else
{
[self jumpNativeViewControllers:url];
}
return YES;
}
3 如果是執行動作的邏輯,獲取query,字符串處理,獲取鍵值對,獲取名稱為"name"的key對應的字符串,字符串對比判斷執行相應的動作
例如:
zqprojectmobile://project/action?name=back,
relativePath為/action,query為name=back,字符串處理后得到字典@{name:back},
代碼:
- (void)jumpActions:(NSURL *)url
{
//dictionaryFromQueryComponents為字符串處理方法,處理query得到字典
NSDictionary *dictionaryQuery = [[url query] dictionaryFromQueryComponents];
NSString *actionName = [dictionaryQuery objectForKey:@"name"];
// 返回前面的頁面
if ([actionName isEqualToString:@"back"])
{
//返回上一個頁面的動作
}
4 如果是執行跳轉頁面的邏輯,可以直接將要跳轉的頁面設置為relativePath的值,然后獲取relativePath字符串進行對比跳轉相應的頁面。如果頁面跳轉需要傳值可以放到query里,獲取url的query,字符串處理,獲取鍵值對,一一賦值,例如:
zqprojectmobile://project/order?order_id=42347645&type=2
relativePath為/order,跳轉到訂單詳情頁面
query為order_id=42347645&type=2,字符串處理獲取字典@{order_id:42347645,type:2},訂單詳情頁面的訂單id為order_id對應的值42347645,訂單類型為2
代碼:
- (void)jumpNativeViewControllers:(NSURL *)url
{
NSString *relativePath = [url relativePath];
if ([relativePath isEqualToString:@"/order"])
{
[self jumpOrder:url];
}
}
- (void)jumpOrder:(NSURL *)url
{
//dictionaryFromQueryComponents為字符串處理方法,處理query得到字典
NSDictionary *dictionaryQuery = [[url query] dictionaryFromQueryComponents];
NSString *orderId = [dictionaryQuery objectForKey:@"orderId"];
NSString *type = [dictionaryQuery objectForKey:@"type"];
// 創建新的訂單頁面并且傳值
}
通過以上幾步就可以定義出一個完整的scheme協議并且在JumpURLHandle類里完成解析
需要用到JumpURLHandle解析scheme的地方
1 APP端收到服務器端下發的PUSH通知欄消息和APP相互跳轉時需要在AppDelegate里處理
// 廢棄
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
{
return [JumpURLHandle parseURL:url];
}
或者
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
return [JumpURLHandle parseURL:url];
}
2 服務器下發跳轉路徑,客戶端根據服務器下發跳轉路徑跳轉相應的頁面,在一個網絡請求成功的回調方法或者block里拿到url,調用[JumpURLHandle parseURL:url];
3 H5頁面點擊錨點,根據錨點具體跳轉路徑APP端跳轉具體的頁面,在UIWebView的代理方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
中調用,
該回調方法返回YES時webView才繼續加載頁面,當我們通過scheme解析處理事件時就不需要再繼續加載頁面返回NO.
UIWebViewNavigationType的類型有:
- UIWebViewNavigationTypeLinkClicked,用戶觸擊了一個鏈接。
- UIWebViewNavigationTypeFormSubmitted,用戶提交了一個表單。
- UIWebViewNavigationTypeBackForward,用戶觸擊前進或返回按鈕。
- UIWebViewNavigationTypeReload,用戶觸擊重新加載的按鈕。
- UIWebViewNavigationTypeFormResubmitted,用戶重復提交表單
- UIWebViewNavigationTypeOther,發生其它行為。
并且不能所有在webView上發生的動作都靠scheme協議解析解決,只有在webView發生用戶點擊事件或者其他行為時我們才根據request.url進一步判斷是否需要scheme解析,代碼如下:
-(BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked
|| navigationType == UIWebViewNavigationTypeOther)
{
if (([[request.URL scheme] isEqualToString:@"zqprojectmobile"])
|| ([[[request.URL scheme] lowercaseString] isEqualToString:@"zqprojectmobile"]))
{
[JumpURLHandle parseURL:request.URL];
return NO;
}
}
return YES;
}