近期在項(xiàng)目中有大量需求需要用WKWebView來(lái)實(shí)現(xiàn),有在項(xiàng)目中嘗試使用WKWebView攔截URL,攔截廣告或者獲取視頻播放地址,以及和h5混合開(kāi)發(fā),有遇到過(guò)一些坑,現(xiàn)在記錄部分典型問(wèn)題以供后續(xù)遇到時(shí)查閱。
1、 WKWebview 攔截http/https請(qǐng)求
1.1、NSURLProtocol方式
WKWebView 在獨(dú)立于 app 進(jìn)程之外的進(jìn)程中執(zhí)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求數(shù)據(jù)不經(jīng)過(guò)主進(jìn)程,所以在 WKWebView 上直接使用 NSURLProtocol 無(wú)法攔截請(qǐng)求。但蘋(píng)果開(kāi)源的 webKit2 源碼暴露了私有API:
+ [WKBrowsingContextController registerSchemeForCustomProtocol:]
使用上面的方法后,如果有post請(qǐng)求,會(huì)丟失request的body。在之前一個(gè)實(shí)際案例中想要獲取視頻播放的真實(shí)播放地址時(shí),發(fā)現(xiàn)不少網(wǎng)站在獲取真實(shí)視頻源前會(huì)有一個(gè)驗(yàn)證,驗(yàn)證用的post請(qǐng)求,此方法會(huì)導(dǎo)致 body丟失,從而無(wú)法獲取真實(shí)地址,網(wǎng)頁(yè)上的播放窗口黑屏,提示:md5失敗。另外此方式也依賴(lài)私有api,伴隨著一定的審核風(fēng)險(xiǎn)。
1.2、WKURLSchemeHandler方式
WKURLSchemeHandler 是 iOS11 就推出的,用于處理自定義請(qǐng)求的方案,不過(guò)并不能處理 HTTP、HTTPS 等常規(guī) scheme。但是現(xiàn)在有另外一個(gè)方法,代碼如下:
#import "ViewController.h"
#import <WebKit/WebKit.h>
@interface CustomURLSchemeHandler : NSObject<WKURLSchemeHandler>
@end
@implementation CustomURLSchemeHandler
//這里攔截到URLScheme為customScheme的請(qǐng)求后,讀取本地圖片test.jpg,并返回給WKWebView顯示
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask{
NSURLRequest *request = urlSchemeTask.request;
//在下面處理攔截到的HTTP和HTTPS請(qǐng)求,處理完后再次發(fā)起請(qǐng)求, 注意要處理didReceiveResponse: didReceiveData: didFinish三個(gè)方法回調(diào)
if ([request.URL.scheme containsString:@"https"]) {
}else if ([request.URL.scheme containsString:@"http"]){
}
// UIImage *image = [UIImage imageNamed:@"test.jpg"];
// NSData *data = UIImageJPEGRepresentation(image, 1.0);
// NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:@"image/jpeg" expectedContentLength:data.length textEncodingName:nil];
// [urlSchemeTask didReceiveResponse:response];
// [urlSchemeTask didReceiveData:data];
// [urlSchemeTask didFinish];
}
- (void)webView:(WKWebView *)webVie stopURLSchemeTask:(id)urlSchemeTask {
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//如下是以APP為視角寫(xiě)的處理WKWebview請(qǐng)求的方式,如果要AOP,就需要hook WKWebview的initwithFrame方法,在里面處理WKWebview的請(qǐng)求
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
CustomURLSchemeHandler *handler = [[CustomURLSchemeHandler alloc]init];
NSMutableDictionary *handlers = [configuration valueForKey:@"_urlSchemeHandlers"];
handlers[@"https"] = handler;//修改handler,將HTTP和HTTPS也一起攔截
handlers[@"http"] = handler;
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
self.view = webView;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
}
@end
該方法接入方便,能較好的解決問(wèn)題,但遺憾的是上面的方法已經(jīng)失效,經(jīng)驗(yàn)證在iOS 12.2以及之后的系統(tǒng)版本中使用上面方法運(yùn)行時(shí)會(huì)報(bào)錯(cuò)。報(bào)錯(cuò)內(nèi)容如下:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<WKWebViewConfiguration 0x121d0eec0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _urlSchemeHandlers.'
2、 WKWebview 白屏問(wèn)題
2.1、白屏?xí)rWKCompositingView 為空的情況
先說(shuō)下我遇到白屏問(wèn)題的場(chǎng)景:在支付頁(yè)面點(diǎn)擊支付喚起微信支付支付后,回到h5頁(yè)面,load一個(gè)新的請(qǐng)求時(shí)遇到。
經(jīng)過(guò)多次模擬和對(duì)比,發(fā)現(xiàn)每次白屏的時(shí)候webview都缺少一個(gè)WKCompositingView的子視圖,針對(duì)此場(chǎng)景解決方案是,在頁(yè)面進(jìn)入前臺(tái)時(shí)遍歷wkwebview的子視圖(等0.5s后遍歷,否則會(huì)發(fā)現(xiàn)每次都缺少該view.),如果沒(méi)有該類(lèi)型的view,則創(chuàng)建新的 webview,然后重新request,記得在創(chuàng)建新的webView時(shí)要先回收到白屏的webView,否則可能會(huì)出現(xiàn)問(wèn)題。
判斷是否白屏代碼如下:
// 判斷是否白屏
- (BOOL)isBlankView:(UIView*)view
{
Class wkCompositingView =NSClassFromString(@"WKCompositingView");
if ([view isKindOfClass:[wkCompositingView class]]) {
return NO;
}
for(UIView*subView in view.subviews) {
if (![self isBlankView:subView])
{
return NO;
}
}
return YES;
}
2.1、其他原因的白屏
在尋找解決方案時(shí)發(fā)現(xiàn)還有其他可能會(huì)導(dǎo)致白屏問(wèn)題,由于我自己沒(méi)有遇到過(guò),暫時(shí)無(wú)法驗(yàn)證。其他原因?qū)е碌陌灼琳?qǐng)看:https://blog.csdn.net/Asia_ZhangQQ/article/details/82812825
3、native/js交互
這里主要記錄一個(gè)數(shù)據(jù)傳遞問(wèn)題,在Apple提供的方法中無(wú)法同步返回?cái)?shù)據(jù)給JS方法,WKWebView不能像UIWebView一樣利用javaScriptCore交互,如果想要使用原生的方法傳遞數(shù)據(jù)給,必須通過(guò)native調(diào)用JS的方式以傳參形式傳遞數(shù)據(jù)??偟脕?lái)說(shuō),有下面幾種方式來(lái)完成數(shù)據(jù)傳遞:
3.1、JS 調(diào)用native方法,native方法中立刻調(diào)用JS方法,通過(guò)傳參的形式將數(shù)據(jù)給到JS。(對(duì)JS來(lái)說(shuō)是異步收到返回值的)
3.2、利用開(kāi)源框架WebViewJavascriptBridge完成(同步)
[self.bridge registerHandler:@"ObjC Echo" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"ObjC Echo called with: %@", data);
responseCallback(data);
}];
[self.bridge callHandler:@"JS Echo" data:nil responseCallback:^(id responseData) {
NSLog(@"ObjC received response: %@", responseData);
}];
3.3、在簡(jiǎn)書(shū)上看到另一種思路,通過(guò)攔截alert的方式,將數(shù)據(jù)傳遞給JS,此方法本人還未嘗試,有興趣的同學(xué)可以試一下。《iOS WKWebView與JS交互》
// 交互??奢斎氲奈谋尽? - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler
{
NSLog(@"%@---%@",prompt,defaultText); completionHandler(@"xxxxx");//這里就是要返回給JS的返回值
}
4、 顯示native異常view(有點(diǎn)擊重試按鈕)和loading框
在某些場(chǎng)景,需要使用原生的loading以及native的異常view,該類(lèi)問(wèn)題主要需要解決以下幾個(gè)問(wèn)題:
4.1、何時(shí)開(kāi)始loading,何時(shí)關(guān)閉loading
- 首次loadrequest時(shí)開(kāi)始loading
- 在異常view,點(diǎn)擊重試按鈕時(shí)開(kāi)始loading
- 在
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation觸發(fā)時(shí)停止loading - 在
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error觸發(fā)時(shí)停止loading - 在
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error觸發(fā)時(shí)停止loading - 另外還有一種場(chǎng)景需要特別強(qiáng)調(diào),如果頁(yè)面在webView上已經(jīng)看到頁(yè)面顯示了,但頁(yè)面還沒(méi)有完全加載完(進(jìn)度沒(méi)有達(dá)到100%),就會(huì)出現(xiàn)loading畫(huà)面疊加在呈現(xiàn)的內(nèi)容上,體驗(yàn)很差,為了減少此情況我們應(yīng)該監(jiān)聽(tīng)頁(yè)面加載進(jìn)度,當(dāng)頁(yè)面加載到一定進(jìn)度時(shí),停止loading。代碼如下:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *, id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
if (self.userWebView.estimatedProgress >= 0.8f) {
if (!_lastRequestIsFail) {
// 請(qǐng)求失敗也會(huì)使進(jìn)度 = 1
[self.loadingView stopLoading];
[self hidenEmptyTip];
}
}
}
}
4.1、何時(shí)顯示異常view,何時(shí)關(guān)閉異常view
- 在
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error觸發(fā)時(shí)顯示異常 - 在
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error觸發(fā)時(shí)顯示異常 - 在
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation觸發(fā)時(shí)隱藏異常 - 在異常view,點(diǎn)擊重試按鈕時(shí)隱藏異常
- 在頁(yè)面進(jìn)度加載到指定值時(shí)隱藏異常