WKWebView踩坑記錄

近期在項(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í)隱藏異常
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容