WKWebview的使用詳解和填坑

雖然WKWebView是在Apple的WWDC 2014隨iOS 8和OS X 10.10出來的,是為了解決UIWebView加載速度慢、占用內(nèi)存大的問題。但是由于之前還要適配iOS7,所以就沒有使用。現(xiàn)在項目都適配iOS 8以上了,所以就開始使用WKWebView了,但是發(fā)現(xiàn)在使用的時候有好多坑,希望這篇文章能帶大家繞過坑,更好的使用WKWebView。
這篇文章主要介紹了以下問題,方便小伙伴們查閱:
1. WKWebView的基本介紹和使用
2. WKWebView 加載本地的HTML ,css ,js
3. WKWebView和JavaScript的交互
4. WKWebView 默認(rèn)不彈出js的alert問題
5.WKWebView 默認(rèn)是不能識別電話號碼
6.WKWebView 攔截js通過window.open() 打開的窗口
7.WKWebView解決文字顯示太小問題
8. 解決WKWebView加載POST請求無法發(fā)送參數(shù)問題

下面開始說第一個問題** WKWebView**基本使用

1.創(chuàng)建 跟UIWebview 一樣

// 創(chuàng)建WKWebView
    WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    // 設(shè)置訪問的URL
    NSURL *url = [NSURL URLWithString:@"http://www.itdecent.cn"];
    // 根據(jù)URL創(chuàng)建請求
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    // WKWebView加載請求
    [webView loadRequest:request];
    // 將WKWebView添加到視圖
    [self.view addSubview:webView];

UIWebView和WKWebView的代理方法做一個對比
1.準(zhǔn)備加載頁面

UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType
 WKNavigationDelegate: - webView:didStartProvisionalNavigation:

2.內(nèi)容開始加載

UIWebViewDelegate: - webViewDidStartLoad:
 WKNavigationDelegate: - webView:didCommitNavigation:

3.頁面加載完成

UIWebViewDelegate: - webViewDidFinishLoad:
 WKNavigationDelegate: - webView:didFinishNavigation:

4.頁面加載失敗

UIWebViewDelegate: - webView:didFailLoadWithError:
 WKNavigationDelegate: - webView:didFailNavigation:withError:
 WKNavigationDelegate: - webView:didFailProvisionalNavigation:withError:

可以看到很簡單,和UIWebView并沒有多少差別,然而性能就刷刷刷的提上去了,是不是很爽呢?如果你只是簡單的集成個Web頁到App,這些已經(jīng)夠了。不過很多時候并沒有那么簡單,還需要處理各種東西,那么接著往后看。

接下來我來說第二個問題 WKWeebView 加載 本地HTML

UIWebview 加載本地的HTML 的問題,在此就不在多說了,想了解的小伙伴可以參考我之前的一篇文章
webView中引入本地html,image,js,css文件的方法http://www.itdecent.cn/p/afc9e6b68090
本文主要說明一下 WKWeebView 加載 本地HTML

當(dāng)使用loadRequest來讀取本地的HTML時,WKWebView是無法讀取成功的,后臺會出現(xiàn)如下的提示:
Could not create a sandbox extension for /
原因是WKWebView是不允許通過loadRequest的方法來加載本地根目錄的HTML文件。
而在iOS9的SDK中加入了以下方法來加載本地的HTML文件:
[WKWebView loadFileURL:allowingReadAccessToURL:]
但是在iOS9以下的版本是沒提供這個便利的方法的。以下為解決方案的思路,就是在iOS9以下版本時,先將本地HTML文件的數(shù)據(jù)copy到tmp目錄中,然后再使用loadRequest來加載。但是如果在HTML中加入了其他資源文件,例如js,css,image等必須一同copy到temp中。這個是最蛋疼的事情了。

解決方法如下

直接copy代碼可以使用

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setNavTitle:self.titleName];
    self.webview = [[WKWebView alloc]initWithFrame:ccr(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT-64)];
    
    NSString *path = [[NSBundle mainBundle] pathForResource:self.loadHtmlName ofType:@"html"];
//下面是加載css 項目不需要的飄過
      NSString *path2 = [[NSBundle mainBundle] pathForResource:@"calculus" ofType:@"css"];
//下面是加載圖片 項目不需要的飄過
    NSString *path3 = [[NSBundle mainBundle] pathForResource:@"headerbg" ofType:@"jpg"];
    if(path){
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
            // iOS9. One year later things are OK.
            NSURL *fileURL = [NSURL fileURLWithPath:path];
            [self.webview loadFileURL:fileURL allowingReadAccessToURL:fileURL];
//注釋的方法和上面的方法是等價的,兩者都可以使用
//            NSString * htmlCont = [NSString stringWithContentsOfFile:path
//                                                                                       encoding:NSUTF8StringEncoding
//                                                                                          error:nil];
//            [self.webview loadHTMLString:htmlCont baseURL:[NSBundle mainBundle].resourceURL];
        } else {
            // iOS8
            
            NSURL *fileURL = [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path]];
            [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path2]];
            [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path3]];
            NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
            [self.webview loadRequest:request];
        }
    }
    
    [self.view addSubview:self.webview];
}

//把圖片和css copy到tmp目錄中

- (NSURL *)fileURLForBuggyWKWebView8:(NSURL *)fileURL {
    NSError *error = nil;
    if (!fileURL.fileURL || ![fileURL checkResourceIsReachableAndReturnError:&error]) {
        return nil;
    }
    // Create "/temp/www" directory
    NSFileManager *fileManager= [NSFileManager defaultManager];
    NSURL *temDirURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:@"www"];
    [fileManager createDirectoryAtURL:temDirURL withIntermediateDirectories:YES attributes:nil error:&error];
    
    NSURL *dstURL = [temDirURL URLByAppendingPathComponent:fileURL.lastPathComponent];
    // Now copy given file to the temp directory
    [fileManager removeItemAtURL:dstURL error:&error];
    [fileManager copyItemAtURL:fileURL toURL:dstURL error:&error];
    // Files in "/temp/www" load flawlesly :)
    return dstURL;
}

當(dāng)時選擇WKWebView就是為了提高性能,但是沒有想到遇到這么多坑,從看iOS 9才解決了iOS 8無法加載本地樣式的問題,有時候蘋果解決問題的速度還有略慢的,到現(xiàn)在POST請求參數(shù)都發(fā)不出去也真是不應(yīng)該。不過沒辦法,先解決了,說不定iOS 10 出來之后解決了呢。

接下來我們開始說第三個問題 WKWebView和JavaScript的交互

WKWebView和JavaScript的交互主要涉及到兩個方面,一個是OC調(diào)用JavaScript ,另一個是 JavaScript 調(diào)用OC的方法,

在WebKit框架中,有WKWebView可以替換UIKit的UIWebView和AppKit的WebView,而且提供了在兩個平臺可以一致使用的接口。WebKit框架使得開發(fā)者可以在原生App中使用Nitro來提高網(wǎng)頁的性能和表現(xiàn),Nitro就是Safari的JavaScript引擎,WKWebView不支持JavaScriptCore的方式但提供message handler的方式為JavaScript與Native通信。(這個引自天狐博客,更多的與UIWebView或者WKWebView的交互方法可以在這里看到)。

1. OC調(diào)用JavaScript

OC調(diào)用JavaScrippt是相對來說比較簡單的
只需要在調(diào)用的地方添加下面一句代碼即可

//showAlert()是js里面的方法,這樣就可以實現(xiàn)調(diào)用js方法
[self.webView evaluateJavaScript:@"showAlert('奏是一個彈框')" completionHandler:^(id item, NSError * _Nullable error) {
        // Block中處理是否通過了或者執(zhí)行JS錯誤的代碼
    }];
2. JavaScript 調(diào)用OC的方法,相對來說復(fù)雜一點

這地方需要兩個配置,一個是OC代碼的配置,另一個是JS代碼的配置,下面先說一下OC代碼的配置,細(xì)心的小伙伴可能已經(jīng)發(fā)現(xiàn)了,創(chuàng)建WKWebView的時候,除了有- initWithFrame:方法外,還有一個高端的方法:- initWithFrame:configuration:方法。
#######OC代碼的配置
1.配置 WKWebView

// 創(chuàng)建配置
      WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
      // 創(chuàng)建UserContentController(提供JavaScript向webView發(fā)送消息的方法)
      WKUserContentController* userContent = [[WKUserContentController alloc] init];
      // 添加消息處理,注意:self指代的對象需要遵守WKScriptMessageHandler協(xié)議,結(jié)束時需要移除
//NativeMethod 這個方法一會要與JS里面的方法寫的一樣
      [userContent addScriptMessageHandler:self name:@"NativeMethod"];
      // 將UserConttentController設(shè)置到配置文件
      config.userContentController = userContent;
      // 高端的自定義配置創(chuàng)建WKWebView
      WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:config];
      // 設(shè)置訪問的URL
      NSURL *url = [NSURL URLWithString:@"http://www.itdecent.cn"];
      // 根據(jù)URL創(chuàng)建請求
      NSURLRequest *request = [NSURLRequest requestWithURL:url];
      // WKWebView加載請求
      [webView loadRequest:request];
      // 將WKWebView添加到視圖
      [self.view addSubview:webView];
2.實現(xiàn)協(xié)議方法

好了,現(xiàn)在萬事俱備,只欠東風(fēng)了。東風(fēng)是什么呢,就是該在哪兒處理??梢钥吹絎KScriptMessageHandler的協(xié)議里面只有一個方法,就是:

 - userContentController:didReceiveScriptMessage:

相信聰明的你已經(jīng)猜到了。是的,就是在這個代理方法里面操作:如果JavaScript執(zhí)行已經(jīng)寫好的:window.webkit.messageHandlers.NativeMethod.postMessage("就是一個消息啊");這行代碼,這個代理方法就會走,并且會有個WKScriptMessage的對象,這個WKScriptMessage對象有個name屬性,拿到之后你會發(fā)現(xiàn),就是我們注冊的NativeMethod這個字符串,這時候你就可以手動調(diào)用Native的方法了。如果有多個方法需要調(diào)用的話怎么辦,看到JavaScript中postMessage()方法有一個參數(shù)了沒有,可以根據(jù)這里的參數(shù)來區(qū)分調(diào)用原生App的哪個方法。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
          // 判斷是否是調(diào)用原生的
          if ([@"NativeMethod" isEqualToString:message.name]) {
              // 判斷message的內(nèi)容,然后做相應(yīng)的操作
              if ([@"close" isEqualToString:message.body]) {

              }
          }
      }
2.JavaScript的配置

JavaScript調(diào)用Native的方法就需要前端和Native的小伙伴們配合了,需要前端的小伙伴在JS的方法中調(diào)用:

window.webkit.messageHandlers.NativeMethod.postMessage("就是一個消息啊");

這行代碼。請注意,這個NativeMethod是和App中要統(tǒng)一的,配置方法將在下面的Native中書寫。
這地方貼一下js代碼

function callOC(func,param){
        window.webkit.messageHandlers. NativeMethod.postMessage('傳遞的參數(shù)');
 }

注意:
第一:實現(xiàn)以上代碼的時候不要忘記實現(xiàn)** WKScriptMessageHandler**協(xié)議
第二:上面將當(dāng)前ViewController設(shè)置為MessageHandler之后需要在當(dāng)前ViewController銷毀前將其移除(dealloc方法),否則會造成內(nèi)存泄漏。

  [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];

第三:來說一下如果有多個方法的時候改如何區(qū)分,我這簡單說兩個區(qū)分方法,一個是通過 [userContent addScriptMessageHandler:self name:@"NativeMethod"];來設(shè)置多個不同的name ,然后在協(xié)議方法里面進(jìn)行區(qū)分,另一個方法是通過 同一個window.webkit.messageHandlers.NativeMethod.postMessage("close");中的name里面的postMessage(來傳遞參數(shù)),然后在協(xié)議方法里面區(qū)分message.body
Android 的小伙伴可以參照這篇文章 http://blog.csdn.net/it1039871366/article/details/46372207
js區(qū)分Android和ios的方法

           var u = navigator.userAgent;
            var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android終端
            var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
          
                if(isAndroid){
                    window.Android.alipayOrder();
                }
                if(isiOS){
                    window.webkit.messageHandlers.alipayOrder.postMessage(r);
                }
第四個問題 WKWebview 默認(rèn)是不彈出js的alert 要想可以彈出alert 需要手動的設(shè)置代理實現(xiàn)
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler

協(xié)議方法
具體的實現(xiàn)方法是,我們采用源生的UIAlertController 來實現(xiàn)彈出框,獲取js里面的alert內(nèi)容顯示出來

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
        NSLog(@"點擊了取消按鈕==%@",message);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認(rèn)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
        NSLog(@"點擊了確定按鈕==%@",message);
    }])];
    
    [self presentViewController:alertController animated:YES completion:nil];
    
}
第五個問題 WKWebview 默認(rèn)是不能識別電話號的,這里需要通過實現(xiàn)一個協(xié)議來實現(xiàn)撥打電話的功能
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

具體的代碼如下:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    NSURL *URL = navigationAction.request.URL;
    NSLog(@"獲取到URL========%@",URL);
    NSString *scheme = [URL scheme];
    UIApplication *app = [UIApplication sharedApplication];
    // 打電話
    if ([scheme isEqualToString:@"tel"]) {
        if ([app canOpenURL:URL]) {
            [app openURL:URL];
            // 一定要加上這句,否則會打開新頁面
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

注:對應(yīng)html代碼采用的是a標(biāo)簽

<a href="tel:18158711698">識別電話號碼18158711698,進(jìn)行撥打電話</a>
問題六: WKWebView 默認(rèn)攔截open.window() 打開新的頁面
- (WKWebView )webView:(WKWebView )webView createWebViewWithConfiguration:(WKWebViewConfiguration )configuration forNavigationAction:(WKNavigationAction )navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
會攔截到window.open()事件.
只需要我們在在方法內(nèi)進(jìn)行處理
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
問題七:WKWebView解決顯示字體太小的問題

在使用WKWebView的時候,常常會碰到顯示內(nèi)容比實際css設(shè)置的樣式不能正常顯示,內(nèi)容普遍的偏小。其實導(dǎo)致這樣問題的根源是少了HTML5的meta標(biāo)簽。解決的辦法可以在iOS端添加以下的內(nèi)容,當(dāng)然也可以讓后臺添加完整的HTML5的格式。如果要在iOS端指定字體的大小也是可以的(不推薦在客戶端設(shè)置字體大?。?。

 NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";

 WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];

WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
wkWebConfig.userContentController = wkUController;

_myWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0,CGRectGetMaxY(headerView.frame)+10, M_S.width,M_S.height - CGRectGetMaxY(headerView.frame) - 40) configuration:wkWebConfig];

客戶端設(shè)置字體大小eg:

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    //修改字體大小 300%
    [ webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '200%'" completionHandler:nil];

    //    //修改字體顏色  #9098b8
    //    [ webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#9098b8'" completionHandler:nil];

}

鄭州高端設(shè)計
推薦博客 http://blog.csdn.net/yuanmengong886/article/details/55051036
https://zhuanlan.zhihu.com/p/24990222

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容