一哩牍、WKWebView簡(jiǎn)介
UIWebView自iOS2就有棚潦,WKWebView從iOS8才有,毫無疑問WKWebView將逐步取代笨重的UIWebView膝昆。通過簡(jiǎn)單的測(cè)試即可發(fā)現(xiàn)UIWebView占用過多內(nèi)存丸边,且內(nèi)存峰值更是夸張。WKWebView網(wǎng)頁加載速度也有提升荚孵,但是并不像內(nèi)存那樣提升那么多妹窖。下面列舉一些其它的優(yōu)勢(shì):
1、更多的支持HTML5的特性
2收叶、官方宣稱的高達(dá)60fps的滾動(dòng)刷新率以及內(nèi)置手勢(shì)
3骄呼、Safari相同的JavaScript引擎,且允許JavaScript的Nitro庫加載并使用(UIWebView中限制)判没;
4蜓萄、將UIWebViewDelegate與UIWebView拆分成了14類與3個(gè)協(xié)議(官方文檔說明)
5、占用更少的內(nèi)存澄峰,在性能绕德、穩(wěn)定性、功能方面有很大提升(最直觀的體現(xiàn)就是加載網(wǎng)頁是占用的內(nèi)存摊阀,模擬器加載百度與開源中國(guó)網(wǎng)站時(shí)耻蛇,WKWebView占用23M踪蹬,而UIWebView占用85M);
另外用的比較多的臣咖,增加加載進(jìn)度屬性:estimatedProgress
二跃捣、WKWebView初始化
1. 首先需要引入WebKit庫
import
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](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也可以通過“曲線救國(guó)”的方式與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:@``"[圖片上傳失敗...(image-947d57-1524901817044)])"``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í)候翘单,做法非常簡(jiǎn)單粗暴,在UIWebView的ScrollView后面添加一個(gè)自定義View蹦渣,然后根據(jù)View的高度哄芜,在改變一下scrollView的contentSize屬性。以為WKWebView也可以這樣簡(jiǎn)單粗暴的去搞一下柬唯,結(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)證簡(jiǎ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)明確給出了交互辦法兑燥。使用起來倒是非常的簡(jiǎn)單亮瓷。創(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中`
`}];`
|
參考資料