前言
Xcode8釋出以後,編譯器開始不支援IOS7,所以很多應用在適配IOS10之後都不在適配IOS7了,其中包括了很多大公司,網易新聞,滴滴出行等。因此,我們公司的應用也打算淘汰IOS7。
支援到IOS8,第一個要改的自然是用WKWebView
替換原來的UIWebView
。WKWebView有很多明顯優勢:
- 更多的支援HTML5的特性
- 官方宣稱的高達60fps的滾動重新整理率以及內建手勢
- 將UIWebViewDelegate與UIWebView拆分成了14類與3個協議,以前很多不方便實現的功能得以實現。文件
- Safari相同的JavaScript引擎
- 佔用更少的記憶體
UIWebView
WKWebView
因此,使用WkWebview
替換UIWebView
還是很有必要的。
基本使用方法
WKWebView有兩個delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要處理一些跳轉、載入處理操作,WKUIDelegate主要處理JS指令碼,確認框,警告框等。因此WKNavigationDelegate更加常用。
比較常用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#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 // 頁面開始載入時呼叫 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{ } // 當內容開始返回時呼叫 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{ } // 頁面載入完成之後呼叫 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ } // 頁面載入失敗時呼叫 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{ } // 接收到伺服器跳轉請求之後呼叫 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{ } // 在收到響應後,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ NSLog(@"%@",navigationResponse.response.URL.absoluteString); //允許跳轉 decisionHandler(WKNavigationResponsePolicyAllow); //不允許跳轉 //decisionHandler(WKNavigationResponsePolicyCancel); } // 在傳送請求之前,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ NSLog(@"%@",navigationAction.request.URL.absoluteString); //允許跳轉 decisionHandler(WKNavigationActionPolicyAllow); //不允許跳轉 //decisionHandler(WKNavigationActionPolicyCancel); } #pragma mark - WKUIDelegate // 建立一個新的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"); } // 確認框 - (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實現js互動 不需要藉助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController實現js native互動。簡單的說就是先註冊約定好的方法,然後再呼叫。
JS呼叫OC方法
oc程式碼(有誤,記憶體不釋放):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@interface ViewController (){ WKWebView * webView; WKUserContentController* userContentController; } @end @implementation ViewController #pragma mark - lifeCircle - (void)viewDidLoad { [super viewDidLoad]; //配置環境 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"];//註冊一個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程式碼如果認證測試一下就會發現dealloc並不會執行,這樣肯定是不行的,會造成記憶體洩漏。原因是[userContentController addScriptMessageHandler:self name:@"sayhello"];
這句程式碼造成無法釋放記憶體。(ps:試了下用weak指標還是不能釋放,不知道是什麼原因。)因此還需要進一步改進,正確的寫法是用一個新的controller來處理,新的controller再繞用delegate繞回來。
oc程式碼(正確寫法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@interface ViewController (){ WKWebView * webView; WKUserContentController* userContentController; } @end @implementation ViewController #pragma mark - lifeCircle - (void)viewDidLoad { [super viewDidLoad]; //配置環境 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程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#import #import @protocol WKDelegate - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; @end @interface WKDelegateController : UIViewController @property (weak , nonatomic) id delegate; @end |
.m程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#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程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<html> <head> <script> function say() { //前端需要用 window.webkit.messageHandlers.註冊的方法名.postMessage({body:傳輸的資料} 來給native傳送訊息 window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'}); } </script> </head> <body> <h1>hello world</h1> <button onclick="say()">say hello</button> </body> </html> |
列印出的log:
1 2 3 4 5 |
name:sayhello body:{ body = "hello world!"; } frameInfo: { URL: http://www.test.com/ }> |
注意點
addScriptMessageHandler
要和removeScriptMessageHandlerForName
配套出現,否則會造成記憶體洩漏。- h5只能傳一個引數,如果需要多個引數就需要用字典或者json組裝。
oc呼叫JS方法
程式碼如下:
1 2 3 4 5 6 7 8 |
- (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{ //say()是JS方法名,completionHandler是非同步回撥block [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@",result); }]; } |
h5程式碼同上。
WebViewJavascriptBridge
一般來說,一個好的UI總有一個大神會開發出一個好的第三方封裝框架。WebViewJavascriptBridge的作者也做了一套支援WKWebView與JS互動的第三方框架:WKWebViewJavascriptBridge。
- cocoaPods: pod ‘WebViewJavascriptBridge’, ‘~> 5.0.5’
- github地址:https://github.com/marcuswestin/WebViewJavascriptBridge
主要方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//初始化方法 + (instancetype)bridgeForWebView:(WKWebView*)webView; + (void)enableLogging; //註冊函式名 - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler; //呼叫函式名 - (void)callHandler:(NSString*)handlerName; - (void)callHandler:(NSString*)handlerName data:(id)data; - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback; //重置 - (void)reset; //設定WKNavigationDelegate - (void)setWebViewDelegate:(id)webViewDelegate; |
基本的實現方法和上面寫的差不多,就是封裝了一下,有興趣的童鞋可以自己pod下來使用。