iOS 的 WKWebView (Object-C)使用全面解析

WKWebView 是蘋果爸爸在 iOS 8.0 公佈的新一代用來展現網頁交互內容的容器,基本出發點是全面替代原來的 UIWebView 。 由於 WKWebView 在加載效率和交互方面大大高出 UIWebView , 並且加上更便捷的使用方式,一經推出就獲得了衆多開發者的推崇。最近由於公司人員調配,和網頁交互的任務落在了我這裏,對WKWebView 通過學習瞭解, 總結一下其實際使用方式。html

1 WKWebView 初始化

WKWebView 初始化包括以下知識點:java

image

經過配置設定的 WKUserContentController 能夠注入 JS 方法,供 WKWebView 中加載的網頁使用。WKPreferences 用來控制網頁內容的基本屬性,好比最小字體、是否容許運行 JS 代碼等。git

2 WKUserContentController 注入 JS 方法的方式

具體實現以下github

WKUserContentController *userC = [[WKUserContentController alloc] init];
    [userC addScriptMessageHandler:self name:@"showMsg"];
    [userC addScriptMessageHandler:self name:@"selectPicture"];
    [userC addScriptMessageHandler:self name:@"postClick"];
    
    WKPreferences *preference = [WKPreferences new];
    preference.minimumFontSize = 10;
    preference.javaScriptCanOpenWindowsAutomatically = true;
    
    WKWebViewConfiguration *config = [WKWebViewConfiguration new];
    config.userContentController = userC;
    config.preferences = preference;
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, _progressView.frame.size.height, SCRREN_WIDTH, self.view.frame.size.height - _progressView.frame.size.height) configuration:config];

複製代碼
3 WKWebView 的代理解析

咱們知道 iOS 中的代理就是 iOS 操做系統將運行週期節點暴露給開發者進行使用,經過 WKWebView 中兩大代理 UIDelegate 和 navigationDelegate ,咱們可以在 WKWebView 加載網頁過程和頁面交互過程當中,加入本身的實現邏輯。代理包含的具體節點以下:web

image
4 WKWebView 的代理方法實現

各具體代理方法的使用方式以下跨域

#pragma mark - WKNavigationDelegate 頁面跳轉和載入

#pragma mark - 頁面跳轉
//收到跳轉動做時, 決定是否跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
    NSURLRequest *request = navigationAction.request;
    NSString *hostname = request.URL.host.lowercaseString;
    if (navigationAction.navigationType == WKNavigationTypeLinkActivated
        && ![hostname containsString:@"baidu.com"]) {
        // 對於跨域,須要手動跳轉
//        [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
        // 不容許web內跳轉
        decisionHandler(WKNavigationActionPolicyCancel);
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    
    NSLog(@"收到跳轉動做時, 決定是否跳轉");
}
//收到服務器響應時, 決定是否跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    decisionHandler(WKNavigationResponsePolicyAllow);
    NSLog(@"收到服務器響應時, 決定是否跳轉");
}

//收到服務器跳轉動做時, 決定是否跳轉
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"收到服務器跳轉動做時, 決定是否跳轉");
}

#pragma mark - WKNavigationDelegate 頁面載入

// 開始請求內容
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"開始請求內容");
}

// 開始請求內容時失敗
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@"開始請求內容時失敗");
}

// 服務器開始返回內容
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"服務器開始返回內容");
}

// 服務器開始返回內容完畢
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"服務器開始返回內容完畢");
    _progressView.hidden = true;
}

// 服務器開始返回內容過程錯誤
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@" 服務器開始返回內容過程錯誤");
    _progressView.hidden = true;
}

// 頁面權限變化
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    
    NSLog(@"頁面權限變化時處理");
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

// 服務器開始返回內容過程終止
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
    NSLog(@"服務器開始返回內容過程終止");
}

複製代碼
5 WKWebView 和 JS 的交互

WKWebView 做爲容器,加載的網頁內容,在響應頁面交互時,不少時候須要調用 Native 的方法。好比,點擊某個按鈕選擇相冊和分享,而後退出包含 WKWebView 的 Controller 等。WKWebView 和 JS 的交互實現方式,邏輯上就是經過二者相互協商好的方法名和參數進行相互調用。bash

首先看一下 HTML 中 JS 的方法,包括 shareClick、shareResult、postClick、cameraClick 和 cameraResult:服務器

<!DOCTYPE html>
<html >
    <meta http-equiv="Content-Type" content="text/html; charset=utf8">
<head>
   
<title>Test</title>

<style>
    a{
        // <!--        text-decoration:none;               /* 去除a標籤自帶下劃線   */-->
       // <!--        border:1px solid #999;-->
        //<!--        background-color: #F0F0F0;-->
        //<!--        float:left;                         /* 設置浮動 */-->
        //color:blue;
        text-align:center;
        margin:2px 5px;
        width:100px;
        height:20px;
        font-size:80px;
    }

    input{
        width:600px;
        height:80px;
        font-size:30px;
    }

    button{
        width:200px;
        height:100px;
        font-size:30px;
    }
    textarea{
        font-size:20px;
        width:100%;
    }
</style>

<script>
    function shareClick() {        window.webkit.messageHandlers.showMsg.postMessage({title:'uniapp',content:'一切從零開始',url:'https://github.com/uniapp10'});
    }
    //分享回調結果顯示
    function shareResult(channel_id,share_channel,share_url) {
        var content = channel_id+","+share_channel+","+share_url;
        alert(content);
        document.getElementById("returnValue").value = content;
    }

    function postClick() {
        var string = document.getElementById("textValue").value;
        window.webkit.messageHandlers.postClick.postMessage(string);
    }

    //JS執行window.webkit.messageHandlers.Camera.postMessage(<messageBody>)
    function cameraClick() {
        window.webkit.messageHandlers.selectPicture.postMessage(null);
    }

    //調用相冊回調結果顯示
    function cameraResult(result) {
        alert(result);
        document.getElementById("returnValue").value = result;
    }
</script>

</head>

<body>
    <a href="http://www.baidu.com" >跳轉</a>
    <div>
        <form action="#" method="post">
            <input  type="search" placeholder="Quick Search">
            <button type="submit"><span></span>提交表單</button>
        </form>
    </div>
    <div>
        <input  type="text" placeholder="輸入內容" id ="textValue">
        <button type="submit" onclick="postClick()"><span></span>傳遞給OC</button>
    </div>
    <div>
        <div>
            <input type="button" value="分享" onclick="shareClick()" />
        </div>
        <div>
            <input type="button" value="相機" onclick="cameraClick()" />
        </div>
        <div>
            <h1>回調展現區</h1>
            <textarea id ="returnValue" type="value" rows="5">
            </textarea>
        </div>
        
    </div>
</body>

</html>

複製代碼
5.1 WKWebView 調用 JS

WKWebView 調用 JS 十分簡單,直接經過字符串尋找 JS 中的方法名。好比 JS 中包含分享結果的方法:app

//分享回調結果顯示
    function shareResult(channel_id,share_channel,share_url) {
        var content = channel_id+","+share_channel+","+share_url;
        alert(content);
        document.getElementById("returnValue").value = content;
    }
複製代碼

在 OC 中直接調用:異步

NSString *JSResult = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
    //OC調用JS
    [self.webView evaluateJavaScript:JSResult completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        
        if (error) {
            NSLog(@"%@", error);
        }else{
            NSLog(@"%s", __FUNCTION__);
        }
        
    }];

複製代碼
5.2 JS 調用 WKWebView

JS 調用 WKWebView ,首先須要在 WKWebView 中注入 JS 方法,經過 WKUserContentController 能夠實現。好比上面 2 中提早注入了 showMsg 、selectPicture 和 postClick 3 個方法,在 JS 中就能夠經過方法名進行調用:

function shareClick() {        window.webkit.messageHandlers.showMsg.postMessage({title:'uniapp',content:'一切從零開始',url:'https://github.com/uniapp10'});
    }

    function postClick() {
        var string = document.getElementById("textValue").value;
        window.webkit.messageHandlers.postClick.postMessage(string);
    }

    //JS執行window.webkit.messageHandlers.Camera.postMessage(<messageBody>)
    function cameraClick() {
        window.webkit.messageHandlers.selectPicture.postMessage(null);
    }

複製代碼

WKWebView 中處理 JS 中調用的方法,是在 WKUserContentController 中的代理方法中, 經過 WKScriptMessage 類進行區分。其中 name 表示方法名, body 表示js 中傳遞的方法。

#pragma mark - WKScriptMessageHandler js 調用 OC 接口
- (void)userContentController:(WKUserContentController *)userContentController
      didReceiveScriptMessage:(WKScriptMessage *)message{
    
    if ([message.name isEqualToString:@"showMsg"]) {
        [self showMsg:message.body];
    } else if ([message.name isEqualToString:@"selectPicture"]) {
        [self selectPicture];
    }else if ([message.name isEqualToString:@"postClick"]) {
        [self postClick:message.body];
    }
    
}

複製代碼
5.3 WKWebView 和 JS 的交互的注意點

當實現了 WKWebView 的 UIDelegate 方法後,JS 中使用 Alert 方法的彈出框,是不會顯示在界面上的,須要經過 UIDelegate 的代理方法進行 Native 處理:

#pragma mark - WKUIDelegate 頁面是否顯示警告框\確認框\輸入框

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:[NSString stringWithFormat:@"js 調用 alert : %@",message] preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];

    [self presentViewController:alert animated:YES completion:NULL];
    NSLog(@"%@", message);
    
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"confirm" message:@"js 調用 confirm" preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }]];
    [self presentViewController:alert animated:YES completion:NULL];
    
    NSLog(@"%@", message);
    
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"textinput" message:@"js 調用輸入框" preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor redColor];
    }];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler([[alert.textFields lastObject] text]);
    }]];
    
    [self presentViewController:alert animated:YES completion:NULL];
    
}

複製代碼
6 WKWebView 進度條

在 WKWebView 加載過程當中,一般狀況下都須要告知用戶加載進度,提升用戶的等待耐心。經過監聽其 estimatedProgress 屬性配合 iOS 中的 UIProgressView 可以快捷實現。

6.1 WKWebView 註冊監聽
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];

複製代碼
6.2 WKWebView 監聽處理:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if([keyPath isEqualToString:@"estimatedProgress"]) {
        _progressView.progress = _webView.estimatedProgress;
    }
    else{
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

複製代碼
6.3 最後注意移除監聽
- (void)dealloc{
    [self.webView removeObserver:self forKeyPath:@"title" context:NULL];
}

複製代碼
7 其餘

WKWebView 中 JS 和 Native 的交互方法都是異步調用,若是想要實現同步效果,網上介紹的方式是在 JS 中添加代碼進行 UI 線程阻塞,我的不太同意這種方法,徹底能夠經過 JS 調用 Native 方法處理,待 Native 方法處理完畢,主動調用 JS 中方法進行處理便可。

使用過程當中,有其餘疑問點兒,歡迎留言交流~
最後,具體項目 Demo

做爲一個開發者,有一個學習的氛圍和一個交流圈子特別重要,這是個人交流羣[ 點擊進羣],你們有興趣能夠進羣裏一塊兒交流學習


若是您以爲寫得不錯,請給我一個贊!

收錄: [原文地址]
相關文章
相關標籤/搜索