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