初識WKWebView

公司開始讓做一個新iOS項目,由于蘋果的更新需要每次發版本審核,沒法像服務器一樣實時更新,技術部就討論出原生+HTML混合編程的策略。一想到混合編程就毫不猶豫的開始用UIWebView,但是發現點擊按鈕十分卡頓,用戶體驗不是很好。想起蘋果在2014年新出的WKWebView,想試下效果如何,結果讓我很滿意,心里甚是歡喜。但是.......

調用HTML必然牽扯到交互,以前在網上找的很好用的第三方框架(https://github.com/dukeland/EasyJSWebView)不適用WKWebView,剛開始還自以為是的想著把原來的UIWebView的代理方法替換成對應的WKWebView的代理方法就行了,就埋頭在那里替換,替換完之后發現不行,就開始查看WKWebView的官方文檔,還是我的功力太淺,看的一知半解,也不知道應該怎么去做,就只好去網上查找優秀的文章。(我想說的是,我解決問題的思路是有問題的,有點想當然的去做,或許應該先去了解WKWebView,對比著WKWebView和UIWebView的優劣,然后決定用哪個,而不是先做發現問題再去想辦法解決,幸虧現在是把那些坑填滿了。。。如果一開始的選擇是個無底洞,不知道又要做多少無用功了)。以下內容就按照我遇到問題的順序,對WKWebView做一總結:

一? 、WKWebView簡介

WKWebView是現代 WebKit API 在 iOS 8 和 OS X Yosemite 應用中的核心部分。它代替了 UIKit 中的UIWebView和 AppKit 中的WebView,提供了統一的跨雙平臺 API(iOS和OS)。

二、WKWebView新特性

在性能、穩定性、功能方面有很大提升(最直觀的體現就是加載網頁是占用的內存,模擬器加載百度與開源中國網站時,WKWebView占用23M,而UIWebView占用85M);

和 Safari 相同的 JavaScript 引擎,允許JavaScript的Nitro庫加載并使用(UIWebView中限制);

支持了更多的HTML5特性;

自詡擁有 60fps 滾動刷新率、內置手勢、高效的 app 和 web 信息交換通道

將UIWebViewDelegate與UIWebView重構成了14類與3個協議(具體查看蘋果官方文檔);

三、WKWebView不足


WKWebView會忽視默認的網絡存儲, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。WKWebView有自己的進程,同樣也有自己的存儲空間用來存儲cookie和cache, 其他的網絡類如NSURLConnection是無法訪問到的。同時WKWebView發起的資源請求也是不經過NSURLProtocol的,導致無法自定義請求。同一個應用,不同WKWebView之間的cookie同步,可以通過使用同一個WKProcessPool實現cookie的同步。(我沒有具體實現,如果想嘗試,可以參考http://mcfeng.me/blog/2014/11/03/wkwebview-uiwebview-cookie-sync/)

簡單來說就是頁面之間cookie同步和共享,以前同一個應用,不同UIWebView之間的Cookie是自動同步的。它們都是保存在NSHTTPCookieStorage中。當UIWebView加載一個URL的時候,在加載完成時候,這個URL response中包含的cookie會自動以NSHttpCookie的形式保存到NSHTTPCookieStorage中。同時,如果在http response中,對cookie進行更新或者刪除的話,其結果也會直接反應到NSHTTPCookieStorage存儲的cookie數據中。所以,如果需要取出cookie數值,只要調用[NSHTTPCookieStorage sharedHTTPCookieStorage]即可。但是現在需要手動的設置cookie來保持頁面之間信息同步。

四、WKWebView與HTML交互

WKWebView調用js腳本(以下為示例代碼,作為參考)

```

#pragma mark - WKNavigationDelegate

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

NSString *executeString = @"$(document).ready(function(){$('.guanliansearch').removeClass(\"hide\").addClass(\"show\") });";

[self.webView evaluateJavaScript:executeString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

}];

}

```

HTML調用OC方法

```
UIViewController<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>中WKWebView的初始化,注意要遵守協議

- (void)viewDidLoad {

[super viewDidLoad];

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

[configuration.userContentController addScriptMessageHandler:self name:@"goBack"];(goBack為方法名,與html中對應)

self.webView = [[WKWebView alloc] initWithFrame:viewRect configuration:configuration];

self.webView.navigationDelegate = self;

self.webView.UIDelegate = self;

self.webView.customUserAgent = kCustomUserAgent;


//讓HTML適配屏幕

self.edgesForExtendedLayout = UIRectEdgeNone;

self.automaticallyAdjustsScrollViewInsets = NO;

[self.view addSubview:self.webView];

//顯示加載頁

[self showLoadingView];

// 1. URL 定位資源,需要資源的地址

NSURL *url = [NSURL URLWithString:self.webViewUrl];

// 2. 把URL告訴給服務器,請求,從m.baidu.com請求數據

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

// 設置緩存策略

request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

// 3. 發送請求給服務器

[self.webView loadRequest:request];

}```

```

#pragma mark - WKScriptMessageHandler

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

NSLog(@"%@",NSStringFromSelector(_cmd));

NSLog(@"message.body--%@? message.name--%@",message.body,message.name);

NSArray *messageArray = (NSArray *)message.body;(如果請求為多個參數,強轉為數組)

if ([message.name isEqualToString:@"goBack"]){

[self? goBack];

}

}

- (void)goBack{

[self dismissViewControllerAnimated:YES completion:^{

}];

}```

HTML對應的配置

```

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<script type="text/javascript">//JS響應方法列表>

function btnClick1() {

var useragent = navigator.userAgent;

if(useragent == “***”){

window.webkit.messageHandlers.goBack.postMessage(null);(沒有參數的話必須寫null,如果不寫按鈕點擊也沒有反應,如果多個參數寫法( ['param1','param2'])? ,即用數組格式;一個參數格式(‘param1’),goBack為方法名)

}

}</script>

<style type="text/css">body {background-color:white;font-size:20px;padding:10;}</style>

li {padding:5}

button { width:700; height:100;margin:10}

</head>

<body><ul><li><button type="button" onclick="btnClick1()">showToast></button></li></ul></body>

</html>

```

五,WKWebView? 的WKUIDelegate方法的實現


```

#pragma mark - WKUIDelegate(我只是實現了2個作為參考,3個方法分別對應JavaScript 中創建三種消息框:警告框、確認框、提示框,如果不實現相應的方法,則HTML中寫的消息框無反應)

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

WBLog(@"webView.URL---%@",webView.URL);

UIAlertController *alertController1 = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *noAction? = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler();(該方法一定要實現,否則會系統會崩潰,錯誤日志很奇葩)

}];

[alertController1? addAction:noAction];

[self presentViewController:alertController1? animated:YES completion:^{

}];

}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *cancelAction? = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler(WKNavigationResponsePolicyCancel);

}];

[alertController? addAction:cancelAction];

[self presentViewController:alertController? animated:YES completion:^{

}];

UIAlertAction *okAction? = [UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler(WKNavigationResponsePolicyAllow);

}];

[alertController? addAction:okAction];

}

```

五,WKWebView? 的cookie共享


說道cookie肯定會想到session,首先簡單了解下:(參考資料:http://blog.csdn.net/fangaoxin/article/details/6952954/)

會話(Session)跟蹤是Web程序中常用的技術,用來跟蹤用戶的整個會話。常用的會話跟蹤技術是Cookie與Session。Cookie通過在客戶端記錄信息確定用戶身份Session通過在服務器端記錄信息確定用戶身份

Cookie意為“甜餅”,是由W3C組織提出,最早由Netscape社區發展的一種機制。目前Cookie已經成為標準,所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。

由于HTTP是一種無狀態的協議,服務器單從網絡連接上無從知道客戶身份。怎么辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工作原理

Session是另一種記錄客戶狀態的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上。客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態就可以了。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務器上的“客戶明細表”來確認客戶身份。Session相當于程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。

以下是我手動設置cookie的代碼,(希望有更好的方式,可以多多指教),思路是,當用戶登錄完成之后,保存頁面返回的cookie,主要三個屬性,JSESSIONID,uid,token
用戶登錄完成之后,每次訪問頁面都攜帶有這3個屬性的cookie

```

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{

NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;

NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];

for (NSHTTPCookie *cookie in cookies) {

//每次都存最新的sessionId

[SettingBaseTool deleteDatasByKey:[cookie name]];

[SettingBaseTool saveBaseInfoStringWithKey:[cookie name] value:[cookie value] isDelayUserId:NO];

}

//跳轉之前傳入cookie

//js函數

NSString *JSFuncString =

@"function setCookie(name,value,expires)\

{\

var oDate=new Date();\

oDate.setDate(oDate.getDate()+expires);\

document.cookie=name+'='+value+';expires='+oDate;\

}\

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字符串

//取出 JSESSIONID? uid token

NSString *jessionString = @"JSESSIONID";

NSString *uidString = @"uid";

NSString *tokenString = @"token";

NSMutableString *JSCookieString = JSFuncString.mutableCopy;

NSString *jessionValueString = [SettingBaseTool readBaseInfoStringWithKey:jessionString isDelayUserId:NO];(從我自己封裝的方法從沙盒中取出值)

if ([jessionValueString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", jessionString, jessionValueString];

[JSCookieString appendString:excuteJSString];

}

NSString *uidValueString = [SettingBaseTool readBaseInfoStringWithKey:uidString isDelayUserId:NO];

if ([uidValueString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", uidString, uidValueString];

[JSCookieString appendString:excuteJSString];

}

NSString? *tokenVauleString = [SettingBaseTool readBaseInfoStringWithKey:tokenString isDelayUserId:NO];

if ([tokenVauleString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", tokenString, tokenVauleString];

[JSCookieString appendString:excuteJSString];

}

//執行js

[webView evaluateJavaScript:JSCookieString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

decisionHandler(WKNavigationResponsePolicyAllow);

}];

}


#pragma mark - WKNavigationDelegate

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

NSString *str = @"document.cookie";(獲取頁面返回的cookie,然后設置更新)

[webView evaluateJavaScript:str completionHandler:^(id _Nullable result, NSError * _Nullable error) {

if ([result isNotEmpty]) {

NSArray *resultArray? =[result componentsSeparatedByString:@";"];

if (resultArray.count > 0) {

for (NSString *resultString in resultArray) {

NSArray *keyValueArray = [resultString componentsSeparatedByString:@"="];

if (keyValueArray.count > 1) {

[SettingBaseTool updateDatasByKey:[keyValueArray[0] stringByReplacingOccurrencesOfString:@" " withString:@""] value:keyValueArray[1] ];(自己封裝的方法,更新三個屬性)

}

}

}

}

}];

```

六,捕獲<a href:"9998989">

//在發送請求之前,決定是否跳轉? 如果不實現這個代理方法,默認會屏蔽掉打電話等url

```

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

// 允許跳轉

decisionHandler(WKNavigationActionPolicyAllow);

}

//頁面開始加載

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{

NSString *path=[webView.URL absoluteString];

NSString * newPath = [path lowercaseString];

//捕獲URL,然后用UIApplication打開

if ([newPath hasPrefix:@"sms:"] || [newPath hasPrefix:@"tel:"] || [newPath hasPrefix:@"apipays:"] || [newPath hasPrefix:@"mqqwpa:"]) {

UIApplication * app = [UIApplication sharedApplication];

if ([app canOpenURL:[NSURL URLWithString:newPath]]) {

[app openURL:[NSURL URLWithString:newPath]];

}

return;

}

}

```

七,調用第三方,QQ,或者支付寶

點擊QQ和支付寶都沒有反應,思考原因,最后發現由于自定義了

self.webView.customUserAgent? = @“hah-ios”而第三方判斷是瀏覽器還是Android或者iPhone的標準是基于默認值,所以就改成如下

```

[self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {

NSString *defaultAgent = (NSString *)result;

self.webView.customUserAgent = [NSString stringWithFormat:@"%@ hah-ios",defaultAgent];

}];

```

當然代碼有些冗余,有待優化。不過有種撥開烏云見明月的感覺。。頓時感覺壓力小了很多。。。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容