完全抄錄: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 Scheme
if ([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管理
// 獲取儲存的cookies
NSArray *cookies = [PSNetworkDataHelper cookiesForURL:url];
// 每次都設定request的HTTPHeaderField
NSDictionary *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: cookiesString
injectionTime: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 頁面
複製程式碼
######導航欄半透明問題,能不用半透明就不用了,很折騰