WKWebView代理方法解析
今天看一下WKWebView的兩個協議:WKNavigationDelegate 和 WKUIDelegate。
一、WKNavigationDelegate
#pragma mark - WKWebView NavigationDelegate //WKNavigationDelegate - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@"是否允許這個導航"); decisionHandler(WKNavigationActionPolicyAllow); } - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { // Decides whether to allow or cancel a navigation after its response is known. NSLog(@"知道返回內容之後,是否允許載入,允許載入"); decisionHandler(WKNavigationResponsePolicyAllow); } - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation { NSLog(@"開始載入"); self.progress.alpha = 1; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; } - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation { NSLog(@"跳轉到其他的伺服器"); } - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { NSLog(@"網頁由於某些原因載入失敗"); self.progress.alpha = 0; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; } - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation { NSLog(@"網頁開始接收網頁內容"); } - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation { NSLog(@"網頁導航載入完畢"); [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; self.title = webView.title; [webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable ss, NSError * _Nullable error) { NSLog(@"----document.title:%@---webView title:%@",ss,webView.title); }]; self.progress.alpha = 0; } - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { NSLog(@"載入失敗,失敗原因:%@",[error description]); self.progress.alpha = 0; } - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView { NSLog(@"網頁載入內容程序終止"); } //- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { // NSLog(@"receive"); //}
1).
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSLog(@"是否允許這個導航");
decisionHandler(WKNavigationActionPolicyAllow);
}
這個方法是載入網頁第一個執行的方法,因為它要確定是否允許或者取消載入這個導航。這裡有一個列舉WKNavigationActionPolicy,其結構如下:
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
WKNavigationActionPolicyCancel,
WKNavigationActionPolicyAllow,
} API_AVAILABLE(macosx(10.10), ios(8.0));
這裡的兩個列舉確定了這個網頁是否載入,我們只需要在decisionHandler回撥裡面傳入相應的列舉值即可。這裡可以用來處理自己不允許載入的網頁,比如你的app裡面的網頁很多,但是domain只有兩個,如果你只想載入這兩個domain裡面的網頁,其他的domain不載入,那麼可以在這裡進行處理。(可以用來遮蔽移動或聯通運營商推送的網頁,讓其不在app中展示)
這裡還有一個WKNavigationAction。它是一個導航動作, 包含了點選之後的導航動作,我們做過濾的時候可以通過該動作決定是否允許載入。它有兩個關鍵的FrameInfo:
sourceFrame
targetFrame
他們都是WKFrameInfo的例項。該類包含了網頁載入的frame的資訊。該類有一個重要的屬性:mainFrame。它是一個Bool值,用於標識該frame是不是當前網頁的主frame或者是子frame。舉個例子:
當我們第一次開啟百度的時候,navigationAction是這樣的:
<WKNavigationAction: 0x100410fa0; navigationType = -1; syntheticClickType = 0;
request = <NSMutableURLRequest: 0x170019a10> { URL: https://www.baidu.com/ };
sourceFrame = (null); targetFrame = <WKFrameInfo: 0x100401200; isMainFrame = YES; request = (null)>>
它的request url是https://www.baidu.com/。它的sourceFrame是nil,也就是請求導航的frame是空的。它的targetFrame是:
<WKFrameInfo: 0x100401200; isMainFrame = YES; request = (null)>
這裡是在當前的frame開啟,也就是目的的frame。當我點選新聞那個連結的時候,其navigationAction是這樣的:
<WKNavigationAction: 0x1004120b0; navigationType = -1; syntheticClickType = 0; request = <NSMutableURLRequest: 0x17001af30>
{ URL: http://m.news.baidu.com/news?fr=mohome&ssid=0&from=844b&uid=&pu=sz%401320_2001%2Cta%40iphone_1_10.2_3_602&bd_page_type=1
}; sourceFrame = (null); targetFrame = <WKFrameInfo: 0x100414ff0; isMainFrame = YES; request = <NSMutableURLRequest: 0x17001b080>
{ URL: https://www.baidu.com/ }>>
可以看到,此時的導航動作是要請求的
http://m.news.baidu.com/news?fr=mohome&ssid=0&from=844b&uid=&pu=sz%401320_2001%2Cta%40iphone_1_10.2_3_602&bd_page_type=1
也就是網頁版百度新聞的連結。此時它的sourceFrame是nil,它的targetFrame是:
<WKFrameInfo: 0x100414ff0; isMainFrame = YES; request = <NSMutableURLRequest: 0x17001b080> { URL: https://www.baidu.com/ }>
它的目標frame的request是baidu。其中的isMainFrame屬性是YES,說明是主的frame,所以還是在當前的網頁中開啟一個新連結。
此時我們也許會遇到另外一種情況,就是sourceFrame不為空,但是targetFrame為空,這裡如果targetFrame為空,那麼這個就是新建一個window 導航。用我們自己的話來說就是重新打開了一個tab頁面。
就類似這樣,我點選了PC版的網頁連結,然後新開了一個tab,這樣的話sourceFrame就是當前的百度,而targetFrame就是空的。此時(我用這個網頁測試)你會發現這個代理方法不執行了,好尷尬。。。。處理都不知道怎麼處理了。由於新打開了tab,也就意味著我們的請求不在當前的網頁載入了,那麼也無法呼叫到這個代理方法了。因此我們需要重新配置這個新開啟的網頁,這裡就會呼叫:
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
這個代理方法是WKUIDelegate的代理方法,可在下面檢視。
2).開始載入
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
這個方法比較好理解,就是當網頁內容開始載入到web view的時候呼叫,這裡的navigation沒有其他特殊含義,看一下WKNavigation這個類可以知道,他就是NSObject的一個子類,而且裡面沒有任何新增的其他方法或者屬性。
3).
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
我們知道了網頁是否允許載入,那麼一旦不允許,那麼這個載入過程就已經結束了,不會再執行其他的代理方法;如果允許,那麼就會執行開始載入的代理方法,執行完開始載入的代理方法的時候再執行這個代理方法。
根據意思可知,它的作用就是要根據導航的返回資訊來判斷是否載入網頁。我們首先打印出navigationResponse的資訊:
<WKNavigationResponse: 0x100323e50; response = <NSHTTPURLResponse: 0x170226780> { URL: https://www.baidu.com/ } { status code: 200, headers {
"Cache-Control" = "no-cache";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Length" = 20059;
"Content-Type" = "text/html;charset=utf-8";
Date = "Mon, 27 Mar 2017 05:29:56 GMT";
Server = "bfe/1.0.8.18";
"Set-Cookie" = "H_WISE_SIDS=108266_100186_114821_114654_114743_109815_103550_114996_114701_112106_107314_114132_115245_115109_115056_115244_115043_114797_114513_114998_115227_114329_114534_115032_114276_114975_110085; path=/; domain=.baidu.com, BDSVRTM=182; path=/, __bsi=11762462753482193024_00_281_N_N_189_0303_C02F_N_N_Y_0; expires=Mon, 27-Mar-17 05:30:01 GMT; domain=www.baidu.com; path=/";
"Strict-Transport-Security" = "max-age=172800";
Traceid = 149059259607016350822661639119137312881;
} }>
這個response有個屬性叫做forMainFrame,用於標識導航的frame是不是主frame。
navigationResponse的canShowMIMEType屬性用於表示WebKit是否能夠展示返回的MIME型別。
如果我們拿到了返回的資訊,發現這些資訊我們不需要,我們可以在此方法裡面進行處理,然後決定是否載入該網頁。
4).
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
這個代理方法是在網頁開始接受網路內容的時候呼叫,也就是網路內容開始要往網頁中載入。
5).
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
意思就是這個導航我們已經載入完成了。我的理解就是這個當前網頁載入完畢。
6).網頁載入失敗方法:
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
7).重定向跳轉我們也許會經常遇到,但是一般不怎麼去處理其內容:
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
8).程序終止:
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));
9).一個身份驗證的:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
接下來就來看一下各個代理方法的執行順序:
2017-03-27 14:16:22.028051 WKWebViewDemo[561:39556] 是否允許這個導航
2017-03-27 14:16:22.028486 WKWebViewDemo[561:39556] 開始載入
2017-03-27 14:16:24.102160 WKWebViewDemo[561:39556] 知道返回內容之後,是否允許載入,允許載入
2017-03-27 14:16:24.106276 WKWebViewDemo[561:39556] 網頁開始接收網頁內容
2017-03-27 14:16:29.156518 WKWebViewDemo[561:39556] 網頁導航載入完畢
2017-03-27 14:16:29.177006 WKWebViewDemo[561:39556] ----document.title:百度一下---webView title:百度一下
二、WKUIDelegate
該協議的代理方法的作用是使用原生的使用者介面來代表網頁。 說白了就是我們去手動寫新載入頁面的UI、alert框的樣式、對話方塊的樣式等等。直接拿下面的例子來看:
#pragma mark - WKWebView WKUIDelegate
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
NSLog(@"建立一個新的webView");
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定1" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)webViewDidClose:(WKWebView *)webView {
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
completionHandler(YES);
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
completionHandler(@"oc物件");
}
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo {
return YES;
}
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController {
NSLog(@"Called when the user performs a pop action on the preview.");
}
首先來看一下:
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
在上面也提到了,如果我們新打開了一個tab,就會呼叫此方法。如果我們開啟tab但是沒有實現這個方法,那麼網頁就會取消這個導航,也就是沒有做出任何反應。所以使用WKWebView的時候一定要記得對這個方法進行處理,如果沒有處理也許就會導致點選網頁沒有任何反應。此時可以這樣處理:
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
NSLog(@"建立一個新的webView");
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
這樣做就是說如果是新開啟的網頁,那麼我們可以直接在當前網頁去載入要load的請求,然後返回nil,這樣就是在當前的網頁去開啟新的tab頁面了。也可以直接判斷targetFrame是否空來進行loadRequest。
當我們的網頁中呼叫alert()方法的時候,我們就要去實現:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
如果我們沒有實現這個方法,那麼alert是沒有辦法彈出來的(我這邊測試的是彈不出來)。因此我們可以這樣實現此代理方法:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:nil];
}
我們將alert的UI設定為我們系統的UIAlertController。這裡的message就是要alert出來的資料資訊。
接下來是:
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
completionHandler(YES);
}
此方法的作用是展示一個js確認框。這裡的completionHandler的回撥是確認框的yes or no。例如一個網頁的按鈕處理操作如下:
// confirm選擇框
function firm() {
var r=confirm("Press a button")
if (r==true)
{
document.write("You pressed OK!")
}
else
{
document.write("You pressed Cancel!")
}
}
如果我們傳入的是YES,那麼就會執行“You pressed OK”,否則就是執行“You pressed Cancel”。
接下來是:
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
這裡如果我們不去手動實現該方法,那麼點選的動作就像我們點選取消,是一樣的效果。
例如 在HTML中button的click方法如下:
function prom() {
var result = prompt("演示一個帶輸入的對話方塊", "請輸入內容");
if(result) {
alert("謝謝使用,你輸入的是:" + result)
}
}
那麼這個result就是我們在上面的代理方法裡面傳入的result。例如:
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
completionHandler(@"oc物件");
}
那麼他就會彈出:“謝謝使用,你輸入的是oc物件”。
其實這裡我們可以對上面的三個代理方法進行深度定製UI,那麼就能按照我們原生的UI顯示出來效果。
當我們設定WKWebView的屬性allowsLinkPreview為YES的時候,那麼我們就可以進行3D touch預覽。預設的值是NO。如果我們設定YES,但是:
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo {
return NO;
}
此代理方法中返回NO,那麼依然無法展示預覽檢視,因為該代理方法的作用就是決定是否允許載入預覽檢視。(safari預設是支援3D touch預覽的)。而:
- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions {
return nil;
}
這個方法就是用於我們自己定義預覽介面。另外還有:
- (void)webViewDidClose:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));
和
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController API_AVAILABLE(ios(10.0));
前者是DOM window成功關閉的時候呼叫。後者是當用戶在預覽中執行彈出操作時呼叫。