一、WKWebView簡介
UIWebView自iOS2就有孩擂,WKWebView從iOS8才有喂窟,毫無疑問WKWebView將逐步取代笨重的UIWebView纽帖。通過簡單的測試即可發(fā)現(xiàn)UIWebView占用過多內(nèi)存抡四,且內(nèi)存峰值更是夸張柜蜈。WKWebView網(wǎng)頁加載速度也有提升,但是并不像內(nèi)存那樣提升那么多指巡。下面列舉一些其它的優(yōu)勢:
1淑履、更多的支持HTML5的特性
2、官方宣稱的高達(dá)60fps的滾動(dòng)刷新率以及內(nèi)置手勢
3藻雪、Safari相同的JavaScript引擎秘噪,且允許JavaScript的Nitro庫加載并使用(UIWebView中限制);
4勉耀、將UIWebViewDelegate與UIWebView拆分成了14類與3個(gè)協(xié)議(官方文檔說明)
5指煎、占用更少的內(nèi)存,在性能便斥、穩(wěn)定性至壤、功能方面有很大提升(最直觀的體現(xiàn)就是加載網(wǎng)頁是占用的內(nèi)存,模擬器加載百度與開源中國網(wǎng)站時(shí)椭住,WKWebView占用23M崇渗,而UIWebView占用85M);
另外用的比較多的京郑,增加加載進(jìn)度屬性:estimatedProgress
二、WKWebView初始化
1. 首先需要引入WebKit庫
import <WebKit/WebKit.h>
2. 初始化方法分為以下兩種
// 默認(rèn)初始化
- (instancetype)initWithFrame:(CGRect)frame;
// 根據(jù)對(duì)webview的相關(guān)配置葫掉,進(jìn)行初始化
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
3. 加載網(wǎng)頁與HTML代碼的方式與UIWebView相同些举,代碼如下:
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
[self.view addSubview:webView];
三、屬性介紹
WKBackForwardList:之前訪問過的 web頁面的列表俭厚,可以通過后退和前進(jìn)動(dòng)作來訪問到户魏。
WKBackForwardListItem: webview中后退列表里的某一個(gè)網(wǎng)頁。
WKFrameInfo: 包含一個(gè)網(wǎng)頁的布局信息。
WKNavigation: 包含一個(gè)網(wǎng)頁的加載進(jìn)度信息叼丑。
WKNavigationAction:包含可能讓網(wǎng)頁導(dǎo)航變化的信息关翎,用于判斷是否做出導(dǎo)航變化。
WKNavigationResponse:包含可能讓網(wǎng)頁導(dǎo)航變化的返回內(nèi)容信息鸠信,用于判斷是否做出導(dǎo)航變化纵寝。
WKPreferences: 概括一個(gè) webview 的偏好設(shè)置。
WKProcessPool: 表示一個(gè) web 內(nèi)容加載池星立。
WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法爽茴。
WKScriptMessage: 包含網(wǎng)頁發(fā)出的信息。
WKUserScript:表示可以被網(wǎng)頁接受的用戶腳本绰垂。
WKWebViewConfiguration: 初始化 webview 的設(shè)置室奏。
WKWindowFeatures: 指定加載新網(wǎng)頁時(shí)的窗口屬性。
WKNavigationDelegate: 提供了追蹤主窗口網(wǎng)頁加載過程和判斷主窗口和子窗口是否進(jìn)行頁面加載新頁面的相關(guān)方法劲装。
WKScriptMessageHandler: 提供從網(wǎng)頁中收消息的回調(diào)方法胧沫。
WKUIDelegate: 提供用原生控件顯示網(wǎng)頁的方法回調(diào)。
四占业、WKWebView代理
WKWebView有兩個(gè)delegate,WKUIDelegate 和 WKNavigationDelegate绒怨。WKNavigationDelegate主要處理一些跳轉(zhuǎn)、加載處理操作纺酸,WKUIDelegate主要處理JS腳本窖逗,確認(rèn)框,警告框等餐蔬。因此WKNavigationDelegate更加常用碎紊。
1. WKNavigationDelegate
該代理提供的方法,可以用來追蹤加載過程(頁面開始加載樊诺、加載完成仗考、加載失敗)词爬、決定是否執(zhí)行跳轉(zhuǎn)秃嗜。
#pragma mark - WKNavigationDelegate
// 頁面開始加載時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
// 當(dāng)內(nèi)容開始返回時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
}
// 頁面加載完成之后調(diào)用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
}
// 頁面加載失敗時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
}
頁面跳轉(zhuǎn)的代理方法有三種,分為(收到跳轉(zhuǎn)與決定是否跳轉(zhuǎn)兩種)
// 接收到服務(wù)器跳轉(zhuǎn)請(qǐng)求之后調(diào)用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
}
// 在收到響應(yīng)后顿膨,決定是否跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSLog(@"%@",navigationResponse.response.URL.absoluteString);
//允許跳轉(zhuǎn)
decisionHandler(WKNavigationResponsePolicyAllow);
//不允許跳轉(zhuǎn)
//decisionHandler(WKNavigationResponsePolicyCancel);
}
// 在發(fā)送請(qǐng)求之前锅锨,決定是否跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"%@",navigationAction.request.URL.absoluteString);
//允許跳轉(zhuǎn)
decisionHandler(WKNavigationActionPolicyAllow);
//不允許跳轉(zhuǎn)
//decisionHandler(WKNavigationActionPolicyCancel);
}
2. WKUIDelegate
#pragma mark - WKUIDelegate
// 創(chuàng)建一個(gè)新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
return [[WKWebView alloc]init];
}
// 輸入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
completionHandler(@"http");
}
// 確認(rèn)框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
completionHandler(YES);
}
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
NSLog(@"%@",message);
completionHandler();
}
3. WKScriptMessageHandler
這個(gè)協(xié)議中包含一個(gè)必須實(shí)現(xiàn)的方法,這個(gè)方法是提高App與web端交互的關(guān)鍵恋沃,它可以直接將接收到的JS腳本轉(zhuǎn)為OC或Swift對(duì)象必搞。(當(dāng)然,在UIWebView也可以通過“曲線救國”的方式與web進(jìn)行交互囊咏,著名的Cordova框架就是這種機(jī)制)
// 從web界面中接收到一個(gè)腳本時(shí)調(diào)用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
4恕洲、WKWebView加載JS
// 圖片縮放的js代碼
NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '張圖');";
// 根據(jù)JS字符串初始化WKUserScript對(duì)象
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 根據(jù)生成的WKUserScript對(duì)象塔橡,初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[_webView loadHTMLString:@"<head></head>![](http://upload-images.jianshu.io/upload_images/1204112-3c87ed90109ff19f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)"baseURL:nil];
[self.view addSubview:_webView];
五、WKWebView使用過程中的坑
1霜第、WKWebView下面添加自定義View
因?yàn)槲覀冇袀€(gè)需求是在網(wǎng)頁下面在添加一個(gè)View葛家,用來展示此鏈接內(nèi)容的相關(guān)評(píng)論。在使用UIWebView的時(shí)候泌类,做法非常簡單粗暴癞谒,在UIWebView的ScrollView后面添加一個(gè)自定義View,然后根據(jù)View的高度末誓,在改變一下scrollView的contentSize屬性扯俱。以為WKWebView也可以這樣簡單粗暴的去搞一下,結(jié)果卻并不是這樣喇澡。
首先改變WKWebView的scrollView的contentSize屬性迅栅,系統(tǒng)會(huì)在下一次幀率刷新的時(shí)候,再給你改變回原有的晴玖,這樣這條路就行不通了读存。我馬上想到了另一個(gè)辦法,改變scrollView的contentInset這個(gè)系統(tǒng)倒不會(huì)在變化回原來的呕屎,自以為完事大吉让簿。后來過了兩天,發(fā)現(xiàn)有些頁面的部分區(qū)域的點(diǎn)擊事件無法響應(yīng)秀睛,百思不得其解尔当,最后想到可能是設(shè)置的contentInset對(duì)其有了影響,事實(shí)上正是如此蹂安。查來查去椭迎,最后找到了一個(gè)解決辦法是,就是當(dāng)頁面加載完成時(shí)田盈,在網(wǎng)頁下面拼一個(gè)空白的div畜号,高度就是你添加的View的高度,讓網(wǎng)頁多出一個(gè)空白區(qū)域允瞧,自定義的View就添加在這個(gè)空白的區(qū)域上面简软。這樣就完美解決了此問題。具體可參考Demo所寫述暂,核心代碼如下:
self.addView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, addViewHeight)];
self.addView.backgroundColor = [UIColor redColor];
[self.webView.scrollView addSubview:self.addView];
NSString *js = [NSString stringWithFormat:@"\
var appendDiv = document.getElementById(\"AppAppendDIV\");\
if (appendDiv) {\
appendDiv.style.height = %@+\"px\";\
} else {\
var appendDiv = document.createElement(\"div\");\
appendDiv.setAttribute(\"id\",\"AppAppendDIV\");\
appendDiv.style.width=%@+\"px\";\
appendDiv.style.height=%@+\"px\";\
document.body.appendChild(appendDiv);\
}\
", @(addViewHeight), @(self.webView.scrollView.contentSize.width), @(addViewHeight)];
[self.webView evaluateJavaScript:js completionHandler:nil];
2痹升、WKWebView加載HTTPS的鏈接
HTTPS已經(jīng)越來越被重視,前面我也寫過一系列的HTTPS的相關(guān)文章HTTPS從原理到應(yīng)用(四):iOS中HTTPS實(shí)際使用當(dāng)加載一些HTTPS的頁面的時(shí)候畦韭,如果此網(wǎng)站使用的根證書已經(jīng)內(nèi)置到了手機(jī)中這些HTTPS的鏈接可以正常的通過驗(yàn)證并正常加載视卢。但是如果使用的證書(一般為自建證書)的根證書并沒有內(nèi)置到手機(jī)中,這時(shí)是鏈接是無法正常加載的廊驼,必須要做一個(gè)權(quán)限認(rèn)證据过。開始在UIWebView的時(shí)候,是把請(qǐng)求存儲(chǔ)下來然后使用NSURLConnection去重新發(fā)起請(qǐng)求妒挎,然后走NSURLConnection的權(quán)限認(rèn)證通道绳锅,認(rèn)證通過后,在使用UIWebView去加載這個(gè)請(qǐng)求酝掩。
在WKWebView中鳞芙,WKNavigationDelegate中提供了一個(gè)權(quán)限認(rèn)證的代理方法,這是權(quán)限認(rèn)證更為便捷期虾。代理方法如下:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
這個(gè)方法比原來UIWebView的認(rèn)證簡單的多原朝。但是使用中卻發(fā)現(xiàn)了一個(gè)很蛋疼的問題,iOS8系統(tǒng)下镶苞,自建證書的HTTPS鏈接喳坠,不調(diào)用此代理方法。查來查去茂蚓,原來是一個(gè)bug壕鹉,在iOS9中已經(jīng)修復(fù),這明顯就是不管iOS8的情況了聋涨,而且此方法也沒有標(biāo)記在iOS9中使用晾浴,這點(diǎn)讓我感到有點(diǎn)失望。這樣我就又想到了換回原來UIWebView的權(quán)限認(rèn)證方式牍白,但是試來試去脊凰,發(fā)現(xiàn)也不能使用了。所以關(guān)于自建證書的HTTPS鏈接在iOS8下面使用WKWebView加載茂腥,我沒有找到很好的辦法去解決此問題狸涌。這樣我不得已有些鏈接換回了HTTP,或者在iOS8下面在換回UIWebView础芍。如果你有解決辦法杈抢,也歡迎私信我,感激不盡仑性。
3惶楼、WKWebView和JavaScript交互
WKWebView和JavaScript交互,在WKUserContentController.h這個(gè)頭文件中- (void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name;這個(gè)方法的注釋中已經(jīng)明確給出了交互辦法诊杆。使用起來倒是非常的簡單歼捐。創(chuàng)建WKWebView的時(shí)候添加交互對(duì)象,并讓交互對(duì)象實(shí)現(xiàn)WKScriptMessageHandler中的唯一的一個(gè)代理方法晨汹。具體的方式參考Demo中的使用豹储。
// 添加交互對(duì)象
[config.userContentController addScriptMessageHandler:(id)self.ocjsHelper name:@"timefor"];
// 代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
JavaScript調(diào)用Objective-C的時(shí)候,使用window.webkit.messageHandlers.timefor.postMessage({code: '0001', functionName: 'getdevideId'}); Objective-C自動(dòng)對(duì)交互參數(shù)包裝成了WKScriptMessage對(duì)象淘这,其屬性body則為傳送過來的參數(shù)剥扣,name為添加交互對(duì)象的時(shí)候設(shè)置的名字巩剖,以此名字可以過濾掉不屬于自己的交互方法。其中body可以為NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull钠怯。
而Objective-C在回調(diào)JavaScript的時(shí)候佳魔,不能像我原來在 Objective-C與JavaScript交互的那些事這篇文章中寫的那樣,JavaScript傳過來一個(gè)匿名函數(shù)晦炊,Objective-C這邊直接調(diào)用一下就完事鞠鲜。WKWebView沒有辦法傳過來一個(gè)匿名函數(shù),所以回調(diào)方式断国,要么執(zhí)行一段JavaScript代碼贤姆,或者就是調(diào)用JavaScript那邊的一個(gè)全局函數(shù)。一般是采用后者稳衬,至于Web端雖說暴露了一個(gè)全局函數(shù)霞捡,同樣可以把這一點(diǎn)代碼處理的很優(yōu)雅。Objective-C傳給JavaScript的參數(shù)宋彼,可以為Number, String, and Object弄砍。參考如下:
// 數(shù)字
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", number];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 字符串
NSString *js = [NSString stringWithFormat:@"globalCallback(\'%@\')", string];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 對(duì)象
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", @{@"name" : @"timefor"}];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 帶返回值的JS函數(shù)
[self.webView evaluateJavaScript:@"globalCallback()" completionHandler:^(id result, NSError * _Nullable error) {
// 接受返回的參數(shù),result中
}];