1. 程式人生 > >WKWebView代理方法解析

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成功關閉的時候呼叫。後者是當用戶在預覽中執行彈出操作時呼叫。