Xcode8發佈之後,編譯器開始不支持IOS7,因此不少應用在適配IOS10以後都不在適配IOS7了,其中包括了不少大公司,網易新聞,滴滴出行等。所以,咱們公司的應用也打算淘汰IOS7。html
支持到IOS8,第一個要改的天然是用WKWebView
替換原來的UIWebView
。WKWebView有不少明顯優點:前端
更多的支持HTML5的特性git
官方宣稱的高達60fps的滾動刷新率以及內置手勢github
將UIWebViewDelegate與UIWebView拆分紅了14類與3個協議,之前不少不方便實現的功能得以實現。文檔web
Safari相同的JavaScript引擎json
佔用更少的內存服務器
UIWebViewcookie
WKWebViewapp
所以,使用WkWebview
替換UIWebView
仍是頗有必要的。框架
WKWebView有兩個delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要處理一些跳轉、加載處理操做,WKUIDelegate主要處理JS腳本,確認框,警告框等。所以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 // 頁面開始加載時調用 - (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(); }
WKWebview提供了API實現js交互 不須要藉助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController實現js native交互。簡單的說就是先註冊約定好的方法,而後再調用。
oc代碼(有誤,內存不釋放):
@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代碼(正確寫法):
@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代碼:
#import #import @protocol WKDelegate - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; @end @interface WKDelegateController : UIViewController @property (weak , nonatomic) id 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代碼:
function say() { //前端須要用 window.webkit.messageHandlers.註冊的方法名.postMessage({body:傳輸的數據} 來給native發送消息 window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'}); } hello world say hello
打印出的log:
name:sayhello body:{ body = "hello world!"; } frameInfo:<wkframeinfo: 0x7f872060ce20; ismainframe = yes; request = { url: http: www.test.com="" }="">
addScriptMessageHandler
要和removeScriptMessageHandlerForName
配套出現,不然會形成內存泄漏。
h5只能傳一個參數,若是須要多個參數就須要用字典或者json組裝。
代碼以下:
- (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{ //say()是JS方法名,completionHandler是異步回調block [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@",result); }]; }
h5代碼同上。
通常來講,一個好的UI總有一個大神會開發出一個好的第三方封裝框架。WebViewJavascriptBridge的做者也作了一套支持WKWebView與JS交互的第三方框架:WKWebViewJavascriptBridge。
cocoaPods: pod 'WebViewJavascriptBridge', '~> 5.0.5'
github地址:https://github.com/marcuswestin/WebViewJavascriptBridge
主要方法以下:
//初始化方法 + (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下來使用。
之前UIWebView會自動去NSHTTPCookieStorage中讀取cookie,可是WKWebView並不會去讀取,所以致使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]; } //刪除最後一個「;」 [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)]; return cookieString; }
可是這隻能解決第一次進入的cookie問題,若是頁面內跳轉(a標籤等)仍是取不到cookie,所以還要再加代碼。
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { //取出cookie NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; //js函數 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+'=({FNXX==XXFN}*)(;|$)'));\ 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]; } //執行js [webView evaluateJavaScript:JSCookieString completionHandler:nil]; }