javascript
OC WKWebView的JS与OC交互、Cookie管理
完全抄錄:iOS中UIWebView與WKWebView、JavaScript與OC交互、Cookie管理看我就夠(中)
####WKWebView 是Apple于iOS 8.0推出的WebKit中的核心控件,用來替UIWebView。 WKWebView比UIWebView的優勢在于:
1、更多的支持HTML5的特性 2、高達60fps的滾動刷新率以及內置手勢 3、與Safari相同的JavaScript引擎 4、將UIWebViewDelegate與UIWebView拆分成了14類與3個協議(官方文檔說明) 5、可以獲取加載進度:estimatedProgress(UIWebView需要調用私有Api)
WKUserContentController
@property (nonatomic, strong) WKUserContentController *userContentController; 復制代碼@interface WKUserContentController : NSObject <NSCoding> //讀取添加過的腳本 @property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts; //添加腳本 - (void)addUserScript:(WKUserScript *)userScript; //刪除所有添加的腳本 - (void)removeAllUserScripts; //通過window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 來實現js->oc傳遞消息,并添加handler - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name; //刪除handler - (void)removeScriptMessageHandlerForName:(NSString *)name; @end 復制代碼創建一個WKWebView的代碼如下:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; WKUserContentController *controller = [[WKUserContentController alloc] init]; configuration.userContentController = controller; self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration]; self.webView.allowsBackForwardNavigationGestures = YES; //允許右滑返回上個鏈接,左滑前進 self.webView.allowsLinkPreview = YES; //允許鏈接3D Touch self.webView.customUserAgent = @"WebViewDemo/1.0.0"; //自定義UA,UIWebView就沒有此功能,后面會講到通過其他方式實現 self.webView.UIDelegate = self; self.webView.navigationDelegate = self; [self.view addSubview:self.webView]; 復制代碼一些屬性
@property (nullable, nonatomic, readonly, copy) NSString *title; //頁面的title,終于可以直接獲取了 @property (nullable, nonatomic, readonly, copy) NSURL *URL; //當前webView的URL @property (nonatomic, readonly, getter=isLoading) BOOL loading; //是否正在加載 @property (nonatomic, readonly) double estimatedProgress; //加載的進度 @property (nonatomic, readonly) BOOL canGoBack; //是否可以后退,跟UIWebView相同 @property (nonatomic, readonly) BOOL canGoForward; //是否可以前進,跟UIWebView相同復制代碼#####動態注入JS
給每個頁面添加一個Cookie
//注入一個Cookie WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'DarkAngelCookie=DarkAngel;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [controller addUserScript:newCookieScript];復制代碼然后再注入一個腳本,每當頁面加載,就會alert當前頁面cookie,在OC中的實現
//創建腳本 WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:@"alert(document.cookie);" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO]; //添加腳本 [controller addUserScript:script]; 復制代碼#####OC -> JS
[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) {NSLog(@"調用evaluateJavaScript異步獲取title:%@", title); }]; 復制代碼#####JS -> OC 1、URL攔截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {//可以通過navigationAction.navigationType獲取跳轉類型,如新鏈接、后退等NSURL *URL = navigationAction.request.URL;//判斷URL是否符合自定義的URL Schemeif ([URL.scheme isEqualToString:@"darkangel"]) {//根據不同的業務,來執行對應的操作,且獲取參數if ([URL.host isEqualToString:@"smsLogin"]) {NSString *param = URL.query;NSLog(@"短信驗證碼登錄, 參數為%@", param);decisionHandler(WKNavigationActionPolicyCancel);return;}}decisionHandler(WKNavigationActionPolicyAllow);NSLog(@"%@", NSStringFromSelector(_cmd)); } 復制代碼2、WKScriptMessageHandler OC中添加一個WKScriptMessageHandler,則會在all frames中添加一個js的function
(1)OC中監聽JS的方法
// 監聽JS的currentCookies方法 [controller addScriptMessageHandler:self name:@"currentCookies"]; //這里self要遵循協 WKScriptMessageHandler 復制代碼(2)JS中調用下面的方法時
// JS調用currentCookies的時候寫上這句代碼 window.webkit.messageHandlers.currentCookies.postMessage(document.cookie); 復制代碼(3)OC中將會收到WKScriptMessageHandler的回調
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {if ([message.name isEqualToString:@"currentCookies"]) {NSString *cookiesStr = message.body; //message.body返回的是一個id類型的對象,所以可以支持很多種js的參數類型(js的function除外)NSLog(@"當前的cookie為: %@", cookiesStr);} } 復制代碼當然,記得在適當的地方調用removeScriptMessageHandler
- (void)dealloc {//記得移除[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"currentCookies"]; } 復制代碼######實現JS -> OC的調用,并且OC -> JS 異步回調結果,這里還是拿分享來舉個例子
JS
/*** 分享方法,并且會異步回調分享結果* @param {對象類型} shareData 一個分享數據的對象,包含title,imgUrl,link以及一個回調function* @return {void} 無同步返回值*/function shareNew(shareData) {//這是該方法的默認實現,上篇文章中有所提及var title = shareData.title;var imgUrl = shareData.imgUrl;var link = shareData.link;var result = shareData.result;//do something//這里模擬異步操作setTimeout(function() {//2s之后,回調true分享成功result(true);}, 2000);//用于WKWebView,因為WKWebView并沒有辦法把js function傳遞過去,因此需要特殊處理一下//把js function轉換為字符串,oc端調用時 (<js function string>)(true); 即可shareData.result = result.toString();window.webkit.messageHandlers.shareNew.postMessage(shareData);}function test() {//清空分享結果shareResult.innerHTML = "";//調用時,應該shareNew({title: "title",imgUrl: "http://img.dd.com/xxx.png",link: location.href,result: function(res) {//這里shareResult 等同于 document.getElementById("shareResult")shareResult.innerHTML = res ? "success" : "failure";}});} 復制代碼在html頁面中我定義了一個a標簽來觸發test()函數
<a href="javascript:void(0);" onclick="test()">測試新分享</a> 復制代碼在OC端,實現如下
//首先別忘了,在configuration中的userContentController中添加scriptMessageHandler [controller addScriptMessageHandler:self name:@"shareNew"]; //記得適當時候remove哦//點擊a標簽時,則會調用下面的方法 #pragma mark - WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {if ([message.name isEqualToString:@"shareNew"]) {NSDictionary *shareData = message.body;NSLog(@"shareNew分享的數據為: %@", shareData);//模擬異步回調dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//讀取js function的字符串NSString *jsFunctionString = shareData[@"result"];//拼接調用該方法的js字符串NSString *callbackJs = [NSString stringWithFormat:@"(%@)(%d);", jsFunctionString, NO]; //后面的參數NO為模擬分享失敗//執行回調[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {if (!error) {NSLog(@"模擬回調,分享失敗");}}];});} } 復制代碼那么當我點擊a標簽時,html頁面上過2s,會顯示success,然后再過2s,會顯示failure。 ####Cookie管理
// 獲取保存的cookiesNSArray *cookies = [PSNetworkDataHelper cookiesForURL:url];// 每次都設置request的HTTPHeaderFieldNSDictionary *cookiesDict = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];[request setValue:[cookiesDict objectForKey:@"Cookie"] forHTTPHeaderField:@"Cookie"];// 每次都JS注入NSMutableString *cookiesString = [NSMutableString string];for (NSHTTPCookie *cookie in cookies) {NSString *string = [NSString stringWithFormat:@"%@=%@; ",cookie.name,cookie.value];[cookiesString appendFormat:@"document.cookie='%@';", string];}WKUserScript * cookieScript = [[WKUserScript alloc]initWithSource: cookiesStringinjectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];[self.wkWebView.configuration.userContentController addUserScript:[self setCookie]];[self.wkWebView loadRequest:request]; 復制代碼#####使用WKWebView需要注意的地方 ######Cookie問題 ######JS彈窗,需要全部現實WKUIDelegate的代理
#pragma mark - WKUIDelegate - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];[alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {completionHandler();}])];[self presentViewController:alertController animated:YES completion:nil];} - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {completionHandler(NO);}])];[alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {completionHandler(YES);}])];[self presentViewController:alertController animated:YES completion:nil]; } - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {textField.text = defaultText;}];[alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {completionHandler(alertController.textFields[0].text?:@"");}])];[self presentViewController:alertController animated:YES completion:nil]; } 復制代碼######白屏問題 當WKWebView加載的網頁占用內存過大時,會出現白屏現象。
解決: A、借助 WKNavigtionDelegate: (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));回調函數: 當 WKWebView 總體內存占用過大,頁面即將白屏的時候,系統會調用上面的回調函數,我們在該函數里執行[webView reload](這個時候 webView.URL 取值尚不為 nil)解決白屏問題。在一些高內存消耗的頁面可能會頻繁刷新當前頁面,H5側也要做相應的適配操作。 B、檢測 webView.title 是否為空:在WKWebView白屏的時候,另一種現象是 webView.titile 會被置空, 因此,可以在 viewWillAppear 的時候檢測 webView.title 是否為空來 reload 頁面 復制代碼######導航欄半透明問題,能不用半透明就不用了,很折騰
總結
以上是生活随笔為你收集整理的OC WKWebView的JS与OC交互、Cookie管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS linker command f
- 下一篇: 23 Merge k Sorted L