WKWebView使用及注意點(keng)
iOS8
之後,蘋果推出了WebKit
這個框架,用來替換原有的UIWebView
,新的控制元件優點多多,不一一敘述。由於一直在適配iOS7
,就沒有去替換,現在仍掉了iOS7
,以為很簡單的就替換過來了,然而在替換的過程中,卻遇到了很多坑。還有一點就是原來寫過一篇文章
Objective-C與JavaScript互動的那些事以為年代久遠的UIWebView
已經作古,可這篇文章現在依然有一定的閱讀量。所以在決定在續一篇此文,以引導大家轉向WKWebView
,並指出自己踩過的坑,讓大家少走彎路。
此篇文章的邏輯圖
此篇文章的邏輯圖
WKWebView使用
WKWebView簡單介紹
使用及注意點
WKWebView
allowsBackForwardNavigationGestures
和載入進度estimatedProgress
等一些UIWebView
不具備卻非常好用的屬性。在建立的時候,指定初始化方法中要求傳入一個WKWebViewConfiguration
物件,一般我們使用預設配置就好,但是有些地方是要根據自己的情況去做更改。比如,配置中的allowsInlineMediaPlayback
這個屬性,預設為NO
,如果不做更改,網頁中內嵌的視訊就無法正常播放。
更改User-Agent
有時我們需要在User-Agent
新增一些額外的資訊,這時就要更改預設的User-Agent
UIWebView
的時候,可用如下程式碼(在使用UIWebView
之前執行)全域性更改User-Agent
:
// 獲取預設User-Agent
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *oldAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
// 給User-Agent新增額外的資訊
NSString *newAgent = [NSString stringWithFormat:@"%@;%@" , oldAgent, @"extra_user_agent"];
// 設定global User-Agent
NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
以上程式碼是全域性更改User-Agent
,也就是說,App
內所有的Web
請求的User-Agent
都被修改。替換為WKWebView
後更改全域性User-Agent
可以繼續使用上面的一段程式碼,或者改為用WKWebView
獲取預設的User-Agent
,程式碼如下:
self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero];
// 獲取預設User-Agent
[self.wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
NSString *oldAgent = result;
// 給User-Agent新增額外的資訊
NSString *newAgent = [NSString stringWithFormat:@"%@;%@", oldAgent, @"extra_user_agent"];
// 設定global User-Agent
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}];
對比發現,這兩種方法並沒有本質的區別,一點小區別在於一個是用UIWebView
獲取的預設User-Agent
,一個是用WKWebView
獲取的預設User-Agent
。上面方法的缺點也是很明顯的,就是App
內所有Web
請求的User-Agent
全部被修改。
在iOS9
,WKWebView
提供了一個非常便捷的屬性去更改User-Agent
,就是customUserAgent
屬性。這樣使用起來不僅方便,也不會全域性更改User-Agent
,可惜的是iOS9
才有,如果適配iOS8
,還是要使用上面的方法。
WKWebView的相關的代理方法
WKWebView
的相關的代理方法分別在WKNavigationDelegate
和WKUIDelegate
以及WKScriptMessageHandler
這個與JavaScript
互動相關的代理方法。
WKNavigationDelegate
: 此代理方法中除了原有的UIWebView
的四個代理方法,還增加了其他的一些方法,具體可參考我下面給出的Demo
。WKUIDelegate
: 此代理方法在使用中最好實現,否則遇到網頁alert
的時候,如果此代理方法沒有實現,則不會出現彈框提示。WKScriptMessageHandler
: 此代理方法就是和JavaScript
互動相關,具體介紹參考下面的專門講解。
WKWebView使用過程中的坑
WKWebView下面新增自定義View
因為我們有個需求是在網頁下面在新增一個View
,用來展示此連結內容的相關評論。在使用UIWebView
的時候,做法非常簡單粗暴,在UIWebView
的ScrollView
後面新增一個自定義View
,然後根據View
的高度,在改變一下scrollView
的contentSize
屬性。以為WKWebView
也可以這樣簡單粗暴的去搞一下,結果卻並不是這樣。
首先改變WKWebView
的scrollView
的contentSize
屬性,系統會在下一次幀率重新整理的時候,再給你改變回原有的,這樣這條路就行不通了。我馬上想到了另一個辦法,改變scrollView
的contentInset
這個系統倒不會在變化回原來的,自以為完事大吉。後來過了兩天,發現有些頁面的部分割槽域的點選事件無法響應,百思不得其解,最後想到可能是設定的contentInset
對其有了影響,事實上正是如此。查來查去,最後找到了一個解決辦法是,就是當頁面載入完成時,在網頁下面拼一個空白的div
,高度就是你新增的View
的高度,讓網頁多出一個空白區域,自定義的View
就新增在這個空白的區域上面。這樣就完美解決了此問題。具體可參考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];
WKWebView載入HTTPS的連結
HTTPS
已經越來越被重視,前面我也寫過一系列的HTTPS
的相關文章HTTPS從原理到應用(四):iOS中HTTPS實際使用當載入一些HTTPS
的頁面的時候,如果此網站使用的根證書已經內建到了手機中這些HTTPS
的連結可以正常的通過驗證並正常載入。但是如果使用的證書(一般為自建證書)的根證書並沒有內建到手機中,這時是連結是無法正常載入的,必須要做一個許可權認證。開始在UIWebView
的時候,是把請求儲存下來然後使用NSURLConnection
去重新發起請求,然後走NSURLConnection
的許可權認證通道,認證通過後,在使用UIWebView
去載入這個請求。
在WKWebView
中,WKNavigationDelegate
中提供了一個許可權認證的代理方法,這是許可權認證更為便捷。代理方法如下:
- (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);
}
}
這個方法比原來UIWebView
的認證簡單的多。但是使用中卻發現了一個很蛋疼的問題,iOS8
系統下,自建證書的HTTPS
連結,不呼叫此代理方法。查來查去,原來是一個bug
,在iOS9
中已經修復,這明顯就是不管iOS8
的情況了,而且此方法也沒有標記在iOS9
中使用,這點讓我感到有點失望。這樣我就又想到了換回原來UIWebView
的許可權認證方式,但是試來試去,發現也不能使用了。所以關於自建證書的HTTPS
連結在iOS8
下面使用WKWebView
載入,我沒有找到很好的辦法去解決此問題。這樣我不得已有些連結換回了HTTP
,或者在iOS8
下面在換回UIWebView
。如果你有解決辦法,也歡迎私信我,感激不盡。
WKWebView載入POST請求
非常感謝@e231e1ff5f8b的指出,原來POST
請求這兒還有一個坑。自己專案中並沒有這塊需求,也就沒有發現。載入POST
請求的時候,會丟失HTTPBody
。解決辦法是在網頁上開一個JavaScript
方法,在請求POST
的時候去呼叫JavaScript
這個方法,從而完成POST
請求。呼叫JavaScript
方法參考下面互動這一章節。
WKWebView和JavaScript互動
WKWebView
和JavaScript
互動,在WKUserContentController.h
這個標頭檔案中- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
這個方法的註釋中已經明確給出了互動辦法。使用起來倒是非常的簡單。建立WKWebView
的時候新增互動物件,並讓互動物件實現WKScriptMessageHandler
中的唯一的一個代理方法。具體的方式參考Demo中的使用。
// 新增互動物件
[config.userContentController addScriptMessageHandler:(id)self.ocjsHelper name:@"timefor"];
// 代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
JavaScript
呼叫Objective-C
的時候,使用window.webkit.messageHandlers.timefor.postMessage({code: '0001', functionName: 'getdevideId'}); Objective-C
自動對互動引數包裝成了WKScriptMessage
物件,其屬性body
則為傳送過來的引數,name
為新增互動物件的時候設定的名字,以此名字可以過濾掉不屬於自己的互動方法。其中body
可以為NSNumber,
NSString, NSDate, NSArray, NSDictionary, and NSNull。
而Objective-C
在回撥JavaScript
的時候,不能像我原來在
Objective-C與JavaScript互動的那些事這篇文章中寫的那樣,JavaScript
傳過來一個匿名函式,Objective-C
這邊直接呼叫一下就完事。WKWebView
沒有辦法傳過來一個匿名函式,所以回撥方式,要麼執行一段JavaScript
程式碼,或者就是呼叫JavaScript
那邊的一個全域性函式。一般是採用後者,至於Web
端雖說暴露了一個全域性函式,同樣可以把這一點程式碼處理的很優雅。Objective-C
傳給JavaScript
的引數,可以為Number,
String, and Object
。參考如下:
// 數字
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", number];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 字串
NSString *js = [NSString stringWithFormat:@"globalCallback(\'%@\')", string];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 物件
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", @{@"name" : @"timefor"}];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 帶返回值的JS函式
[self.webView evaluateJavaScript:@"globalCallback()" completionHandler:^(id result, NSError * _Nullable error) {
// 接受返回的引數,result中
}];
總結
此文主要介紹了WKWebView
使用中的注意點,一般也都是常用的,還有快取等一些不是太常用的就沒有具體介紹。如果在其他方面遇到問題,也歡迎你私信我共同探討進步。WKWebView
確實比UIWebView
有些地方好用不少,但是一些bug
至今也沒解決,許可權挑戰是在iOS9
解決的,POST
請求則至今沒有解決,而改變contentInset
導致的點選事件不準確,同樣是沒有解決。這些問題讓開發者使用起來,有諸多不便啊。
此文的Demo地址:WKWebViewDemo 如果此文對你有所幫助,請給個star
吧。
參考
文/TIME_for(簡書作者)
原文連結:http://www.jianshu.com/p/9513d101e582
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。