雖然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://m.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://m.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://m.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