IOS進(jìn)階之WKWebView

標(biāo)題圖片

前言


Xcode8發(fā)布以后,編譯器開始不支持IOS7,所以很多應(yīng)用在適配IOS10之后都不在適配IOS7了,其中包括了很多大公司,網(wǎng)易新聞,滴滴出行等。因此,我們公司的應(yīng)用也打算淘汰IOS7。

支持到IOS8,第一個(gè)要改的自然是用WKWebView替換原來的UIWebView。WKWebView有很多明顯優(yōu)勢:

  • 更多的支持HTML5的特性

  • 官方宣稱的高達(dá)60fps的滾動刷新率以及內(nèi)置手勢

  • 將UIWebViewDelegate與UIWebView拆分成了14類與3個(gè)協(xié)議,以前很多不方便實(shí)現(xiàn)的功能得以實(shí)現(xiàn)。文檔

  • Safari相同的JavaScript引擎

  • 占用更少的內(nèi)存

UIWebView

UIWebView

WKWebView

WKWebView

因此,使用WkWebview替換UIWebView還是很有必要的。

基本使用方法


WKWebView有兩個(gè)delegate,WKUIDelegateWKNavigationDelegate。WKNavigationDelegate主要處理一些跳轉(zhuǎn)、加載處理操作,WKUIDelegate主要處理JS腳本,確認(rèn)框,警告框等。因此WKNavigationDelegate更加常用。

比較常用的方法:

#pragma mark - lifeCircle
- (void)viewDidLoad {
    [super viewDidLoad];
    webView = [[WKWebView alloc]init];
    [self.view addSubview:webView];
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.right.equalTo(self.view);
        make.top.equalTo(self.view);
        make.bottom.equalTo(self.view);
    }];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];

}

#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{

}
// 接收到服務(wù)器跳轉(zhuǎn)請求之后調(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ā)送請求之前,決定是否跳轉(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);
}
#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();
}

OC與JS交互


WKWebview提供了API實(shí)現(xiàn)js交互 不需要借助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController實(shí)現(xiàn)js native交互。簡單的說就是先注冊約定好的方法,然后再調(diào)用。

JS調(diào)用OC方法

oc代碼(有誤,內(nèi)存不釋放):

@interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{
    WKWebView * webView;
    WKUserContentController* userContentController;
}
@end
@implementation ViewController
#pragma mark - lifeCircle
- (void)viewDidLoad {
    [super viewDidLoad];
    //配置環(huán)境
    WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];
    userContentController =[[WKUserContentController alloc]init];
    configuration.userContentController = userContentController;
    webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];
    //注冊方法
    [userContentController addScriptMessageHandler:self  name:@"sayhello"];//注冊一個(gè)name為sayhello的js方法

    [self.view addSubview:webView];
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.right.equalTo(self.view);
        make.top.equalTo(self.view);
        make.bottom.equalTo(self.view);
    }];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
}
- (void)dealloc{
    //這里需要注意,前面增加過的方法一定要remove掉。
    [userContentController removeScriptMessageHandlerForName:@"sayhello"];
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
}
@end

上面的OC代碼如果認(rèn)證測試一下就會發(fā)現(xiàn)dealloc并不會執(zhí)行,這樣肯定是不行的,會造成內(nèi)存泄漏。原因是[userContentController addScriptMessageHandler:self name:@"sayhello"];這句代碼造成無法釋放內(nèi)存。(ps:試了下用weak指針還是不能釋放,不知道是什么原因。)因此還需要進(jìn)一步改進(jìn),正確的寫法是用一個(gè)新的controller來處理,新的controller再繞用delegate繞回來。

oc代碼(正確寫法):

@interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{
    WKWebView * webView;
    WKUserContentController* userContentController;
}
@end
@implementation ViewController
#pragma mark - lifeCircle
- (void)viewDidLoad {
    [super viewDidLoad];
    //配置環(huán)境
    WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];
    userContentController =[[WKUserContentController alloc]init];
    configuration.userContentController = userContentController;
    webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];
    //注冊方法
    WKDelegateController * delegateController = [[WKDelegateController alloc]init];
    delegateController.delegate = self;

    [userContentController addScriptMessageHandler:delegateController  name:@"sayhello"];

    [self.view addSubview:webView];
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.right.equalTo(self.view);
        make.top.equalTo(self.view);
        make.bottom.equalTo(self.view);
    }];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
}
- (void)dealloc{
    //這里需要注意,前面增加過的方法一定要remove掉。
    [userContentController removeScriptMessageHandlerForName:@"sayhello"];
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
}
@end

WKDelegateController代碼:

#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@protocol WKDelegate <NSObject>

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

@end

@interface WKDelegateController : UIViewController <WKScriptMessageHandler>

@property (weak , nonatomic) id<WKDelegate> delegate;

@end

.m代碼:

#import "WKDelegateController.h"

@interface WKDelegateController ()

@end

@implementation WKDelegateController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
        [self.delegate userContentController:userContentController didReceiveScriptMessage:message];
    }
}


@end

h5代碼:

<html>
<head>
    <script>
function say()
{
//前端需要用 window.webkit.messageHandlers.注冊的方法名.postMessage({body:傳輸?shù)臄?shù)據(jù)} 來給native發(fā)送消息
    window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});
}
</script>
</head>
    <body>
        <h1>hello world</h1>
        <button onclick="say()">say hello</button>
    </body>

</html>

打印出的log:

 name:sayhello
 body:{
    body = "hello world!";
}
 frameInfo:<WKFrameInfo: 0x7f872060ce20; isMainFrame = YES; request =   <NSMutableURLRequest: 0x618000010a30> { URL: http://www.test.com/ }>

注意點(diǎn)

  • addScriptMessageHandler要和removeScriptMessageHandlerForName配套出現(xiàn),否則會造成內(nèi)存泄漏。
  • h5只能傳一個(gè)參數(shù),如果需要多個(gè)參數(shù)就需要用字典或者json組裝。

oc調(diào)用JS方法

代碼如下:

- (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{

    //say()是JS方法名,completionHandler是異步回調(diào)block
    [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@",result);
    }];
    
}

h5代碼同上。

WebViewJavascriptBridge


一般來說,一個(gè)好的UI總有一個(gè)大神會開發(fā)出一個(gè)好的第三方封裝框架。WebViewJavascriptBridge的作者也做了一套支持WKWebView與JS交互的第三方框架:WKWebViewJavascriptBridge。

主要方法如下:

//初始化方法
+ (instancetype)bridgeForWebView:(WKWebView*)webView;
+ (void)enableLogging;

//注冊函數(shù)名
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;

//調(diào)用函數(shù)名
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

//重置
- (void)reset;

//設(shè)置WKNavigationDelegate
- (void)setWebViewDelegate:(id<WKNavigationDelegate>)webViewDelegate;

基本的實(shí)現(xiàn)方法和上面寫的差不多,就是封裝了一下,有興趣的童鞋可以自己pod下來使用。


我是翻滾的牛寶寶,歡迎大家評論交流~

補(bǔ)充


前不久把項(xiàng)目中的UIWebView更新到WkWebView,解決了一大堆問題,但是也遺留了一大堆問題,比方說cookie。

以前UIWebView會自動去NSHTTPCookieStorage中讀取cookie,但是WKWebView并不會去讀取,因此導(dǎo)致cookie丟失以及一系列問題,解決方式就是在request中手動幫其添加上。

mainWebView.UIDelegate = self;
mainWebView.navigationDelegate = self;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]];
[request addValue:[self readCurrentCookieWithDomain:@"http://www.test.com/"] forHTTPHeaderField:@"Cookie"];
[mainWebView loadRequest:request];

- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
    NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSMutableString * cookieString = [[NSMutableString alloc]init];
    for (NSHTTPCookie*cookie in [cookieJar cookies]) {
        [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
    }

//刪除最后一個(gè)“;”
    [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
    return cookieString;
}

但是這只能解決第一次進(jìn)入的cookie問題,如果頁面內(nèi)跳轉(zhuǎn)(a標(biāo)簽等)還是取不到cookie,因此還要再加代碼。

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

   //取出cookie
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    //js函數(shù)
    NSString *JSFuncString =
@"function setCookie(name,value,expires)\
    {\
    var oDate=new Date();\
    oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate+';path=/'\
    }\
    function getCookie(name)\
    {\
    var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\
    if(arr != null) return unescape(arr[2]); return null;\
    }\
    function delCookie(name)\
    {\
    var exp = new Date();\
    exp.setTime(exp.getTime() - 1);\
    var cval=getCookie(name);\
    if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
    }";

    //拼湊js字符串
    NSMutableString *JSCookieString = JSFuncString.mutableCopy;
    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
    NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
        [JSCookieString appendString:excuteJSString];
    }
    //執(zhí)行js
    [webView evaluateJavaScript:JSCookieString completionHandler:nil];

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

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

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