UIWebView和WKWebView與JS的交互詳解

NO IMAGE

參考文章WKWebView官方文檔

簡介

相信很多公司的app都有網頁的嵌入吧,原生APP和JS交互的方式有UIWebView、WKWebView、Cordava、Weex、Flutter、Reactive Native等,我們目前比較常用的是WKWebView,但是本篇文章我準備講解一下UIWebView和WKWebView和JS的交互。

UIWebView繼承自UIView,是iOS內置的瀏覽器控件,可以瀏覽網頁、打開文檔等。能夠加載html、htm、pdf、docx、txt等格式的文件。
iOS8,蘋果新推出了WebKit,用WKWebView代替UIWebView和WebView。相關的使用和特性可以細讀。性能、穩定性、功能大幅度提升
允許JavaScript的Nitro庫加載並使用(UIWebView中限制)、支持了更多的HTML5特性、高達60fps的滾動刷新率以及內置手勢、GPU硬件加速、KVO、重構UIWebView成14類與3個協議。

WKWebView是現代WebKit API在iOS8和OS X Yosemite應用中的核心部分。它代替了UIKit的UIWebView和APPKit中的WebView,提供了統一的跨雙平臺API,目前主要使用WKWebView。

UIWebView和WKWebView與JS的交互詳解

一、UIWebView

1、UIWebView的基本用法

初始化一個UIWebView,並調用UIWebView網頁加載展示的方法,並實現UIWebView的代理方法,基本上就可以實現網頁加載的功能了。

1.1 UIWebView的加載方法


1、//使用 NSURLRequest 的方式加載網頁
- (void)loadRequest:(NSURLRequest *)request;
2、/* 
功能:加載HTML字符串
string為要加載的本地HTML字符串
baseURL用來確定htmlString的基準地址,相當於HTML的<base>標籤的作用,定義頁面中所有鏈接的默認地址
*/
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
3、- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType
textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;

1.2.UIWebView的代理方法

//是否允許加載網頁,也可獲取js要打開的url,通過截取此url可與js交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
navigationType:(UIWebViewNavigationType)navigationType;
//開始加載網頁
- (void)webViewDidStartLoad:(UIWebView *)webView;
//網頁加載完成
- (void)webViewDidFinishLoad:(UIWebView *)webView;
//網頁加載錯誤
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;

2、UIWebView OC調用JS

2.1 stringByEvaluatingJavaScriptFromString:

是一個比較常用的方法,使用起來比較簡單直接,直接調用- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;就可以了,示例如下:

self.title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];

這個方法雖然簡單,但是有很多缺點:

  • 1.該方法不能判斷調用了一個js方法之後,是否發生了錯誤。當錯誤發生時,返回值為nil,而當調用一個方法本身沒有返回值時,返回值也為nil,所以無法判斷是否調用成功了。
  • 2、返回值類型為nullable NSString *,當調用的js方法有返回值時,就都以字符串返回,不夠靈活。當返回值是一個js的Array時,還需要解析字符串,就會比較麻煩。
  • 3、stringByEvaluatingJavaScriptFromString只能在主線程執行。

對於以上的缺點,可以通過使用JavaScriptCore(iOS 7.0 +)來解決。

2.2 JavaScriptCore(iOS 7.0 +)

JSPatch 最近被蘋果禁止在appStore中的應用中使用, JSPatch中最核心的是JavaScriptCore,因為JavaScriptCore的JS到OC的映射,可以將js方法替換成為oc方法,所以其動態性(配合runtime的不安全性)也就成為了JSPatch被Apple禁掉的最主要原因。這裡講下UIWebView通過JavaScriptCore來實現OC調用JS。其實WebKit都有一個內嵌的js環境,一般我們在頁面加載完成之後,獲取js上下文,然後通過JSContext的evaluateScript:方法來獲取返回值。因為該方法得到的是一個JSValue對象,所以支持JavaScript的Array、對象等數據類型。

用法如下:


JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSValue *value = [context evaluateScript:@"document.title"];
self.title = value.toString;

假設我們執行了一個不存在的方法的話,會出現什麼樣的情況呢?比如getSize方法

[self.context evaluateScript:@"document.getSize"];

結果可以知道程序報錯,我們可以通過@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);,設置exceptionHandler來獲取異常。這個方法也很好的解決了程序出現異常之後捕獲不到異常信息的情況,用法如下:


//在調用不存在的方法getSize前,先設置異常回調
[self.context setExceptionHandler:^(JSContext *context, JSValue *exception){
NSLog(@"程序異常為:%@", exception);
}];
//執行getSize方法
JSValue *value = [self.context evaluateScript:@"document.getSize"];

3、UIWebView JS調用OC

3.1 URL攔截方式

假如有一個使用短信驗證碼登錄的功能,html或者js中的方法名為mobileCode,點擊使用短信驗證碼登錄按鈕的時候會捕捉下面的鏈接,解析出所需的參數,從而實現JS
調用OC。

 <a href="mobileCode://smsLogin?username=13678946758&code=122786">使用短信驗證碼登錄</a>

OC代碼中,當打開了一個鏈接,webView會通過代理方法捕捉到鏈接,並且返回NO,從而可以實現我們的OC方法。捕捉不到的話就返回YES,繼續跳轉到html頁面。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = request.URL;    
if ([URL.scheme isEqualToString:@"mobileCode"]) {
if ([URL.host isEqualToString:@"smsLogin"]) {
NSLog(@"使用短信驗證碼登錄,參數為 %@", URL.query);
return NO;
}
}
return YES;
}

3.2 JavaScriptCore (iOS 7.0+)

首先我們在js文件中定義了一個share方法

function share (title, content, imageUrl, url) {
//使用WKWebView測試
window.webkit.messageHandlers.share.postMessage({title: title, content: content, imageUrl: imageUrl, url: url});
//OC實現代碼
}

html中實現一個a標籤調用share方法

<a href="javascript:void(0);" class="sharebtn" onclick="share('領取話費','分享鏈接給你的微信號又或者qq好友,即可領取1元話費' 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1566203866173&di=7a3035ce1c25fb6f1003ca2eeca7f2cd&imgtype=0&src=http%3A%2F%2Fimg1.juimg.com%2F180405%2F355858-1P40511025273.jpg', location.href)">分享領話費</a>

我們在OC中的實現如下

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//    self.title = [self.title stringByAppendingString:[webView stringByEvaluatingJavaScriptFromString:@"document.title"]];
//獲取該UIWebView的javascript上下文
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//這也是一種獲取標題的方法。
JSValue *value = [context evaluateScript:@"document.title"];
//更新標題
self.title = value.toString;
[self convertJSToOCMethod];
}
#pragma mark - 將JS的函數轉換成OC的方法
- (void)convertJSToOCMethod
{
//獲取該UIWebview的javascript上下文
//self持有context
//@property (nonatomic, strong) context *context;
self.context = [self.webView valueForKeyPath:@"context"];
//context oc調用js
//JSValue *value = [self.context evaluateScript:@"document.title"];
//js調用oc
//其中share就是js的方法名稱,賦給是一個block,block中是oc代碼
//此方法最終將打印出所有接收到的參數,js參數是不固定的
self.context[@"share"] = ^() {
//獲取到share方法裡的所有參數array
NSArray *array = [JSContext currentArguments];
//array中的元素JSValue對象轉換為OC對象
NSMutableArray *messages = [NSMutableArray array];
for (JSValue *value in array) {
[messages addObject:[value toObject]];
}
NSLog(@"點擊分享按鈕js傳回的參數如下:\n%@", messages);
};

點擊html中的分享領話費按鈕會在控制檯打印出傳遞參數

4、UIWebView的Cookie管理

Cookie,有時也用其複數形式 Cookies,指某些網站為了辨別用戶身份、進行 session 跟蹤而儲存在用戶本地終端上的數據(通常經過加密)。
UIWebView的cookie一般是由[NSHTTPCookieStorage sharedHTTPCookieStorage]這個單例來管理的,UIWebView會自動同步單例中的Cookie。特殊情況要通過添加Cookie區分的時候可以通過以下幾種方式來實現

  • 添加header實現
 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.LynkCo.com"]];
[request addValue:@"cookitnmae=78965420;" forHTTPHeaderField:@"Set-Cookie"];
[self.webView loadRequest:request];
  • 通過操作NSHTTPCookieStorage添加一個自定義的Cookie
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookieName: @"cookieNmae", 
NSHTTPCookieDomain: @".LynkCo.com",
NSHTTPCookiePath: @"/"
NSHTTPCookieValue: @"78965420", 
}];
//Cookie存在則覆蓋,不存在添加
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];   
  • 讀取所有Cookie,Cookie轉換成HTTPHeaderFields,並添加到request的header中
NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
//Cookies數組轉換為requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//設置請求頭
request.allHTTPHeaderFields = requestHeaderFields;

WKWebView

1、WKWebView的基本用法

1.1 WKWebView 的初始化

整體的初始化示例

- (void)createWebView
{
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]
WKUserContentController *controller = [[WKUserContentController alloc] init];
config.userContentController = controller;
// 根據需要去設置對應的屬性
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
webView.navigationDelegate = self;
[self.view addSubview:webView];    
NSURL *url = [NSURL URLWithString:self.strURL];
[self loadWebViewWithURL:url];    // JS調用OC 添加處理腳本
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"Share"];
}

常用創建方法

- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

介紹以下WKWebViewConfiguration中的兩個比較重要的屬性

  • 屬性 WKWebsiteDataStore *websiteDataStore

WKWebView的一些緩存存儲在websiteDataStore中,修改緩存可以通過WKWebsiteDataStore.h中提供的方法,事實上我們用的比較少,一般情況下清除緩存可以通過刪除沙盒目錄中的Cache文件。

  • 屬性 WKUserContentController *userContentController

js和oc的交互以及動態注入js會用到這個屬性。

1.2 WKWebView的一些類以及類的屬性和方法

  • WKWebView 常用屬性:
// 導航代理
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
// UI代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
// 頁面標題, 一般使用KVO動態獲取
@property (nullable, nonatomic, readonly, copy) NSString *title;
// 頁面加載進度, 一般使用KVO動態獲取
@property (nonatomic, readonly) double estimatedProgress;
// 可返回的頁面列表, 已打開過的網頁, 有點類似於navigationController的viewControllers屬性
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
// 頁面url
@property (nullable, nonatomic, readonly, copy) NSURL *URL;
// 頁面是否在加載中
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
// 是否可返回
@property (nonatomic, readonly) BOOL canGoBack;
// 是否可向前
@property (nonatomic, readonly) BOOL canGoForward;
// WKWebView繼承自UIView, 所以如果想設置scrollView的一些屬性, 需要對此屬性進行配置
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
// 是否允許手勢左滑返回上一級, 類似導航控制的左滑返回
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
//自定義UserAgent, 會覆蓋默認的值 ,iOS 9之後有效
@property (nullable, nonatomic, copy) NSString *customUserAgent
  • WKWebView 常用方法

// 帶配置信息的初始化方法
// configuration 配置信息
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
// 加載請求
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
// 加載HTML
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
// 返回上一級
- (nullable WKNavigation *)goBack;
// 前進下一級, 需要曾經打開過, 才能前進
- (nullable WKNavigation *)goForward;
// 刷新頁面
- (nullable WKNavigation *)reload;
// 根據緩存有效期來刷新頁面
- (nullable WKNavigation *)reloadFromOrigin;
// 停止加載頁面
- (void)stopLoading;
// 執行JavaScript代碼
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

1.3 WKWebView 動態注入JS

示例代碼如下

/* 第一步:通過給userContentController添加WKUserScript,可以實現動態注入js。比如我先注入一個腳本,給每個頁面添加一個Cookie */
//添加自定義的cookie
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"                document.cookie = 'LynkcoCookie=Lynkco;'" 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];

注入的js 資源可以是js字符串,也可以是js文件,比如我們要注入一個js文件ImageClickEvent

/**
頁面中的所有img標籤添加點擊事件
*/
- (void)imgAddClickEvent
{
//防止頻繁IO操作,造成性能影響
static NSString *jsSource;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
jsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ImageClickEvent" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
});
//添加自定義的腳本
WKUserScript *js = [[WKUserScript alloc] initWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[self.webView.configuration.userContentController addUserScript:js];
//註冊回調
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"imageDidClick"];
}

1.4 WKWebView 加載

加載的方法通常有以下幾種

  • -(nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
  • -(nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
  • -(nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
  • -(nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
    但是通常情況下加載本地的html文件不用loadHTMLString:baseURL:方法,要使用loadRequest:方法。

[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@”test” ofType:@”html”]]]];

1.5 WKWebView 代理方法

  • @protocol WKNavigationDelegate; //類似於UIWebView的加載成功、失敗、是否允許跳轉等

  • @protocol WKUIDelegate; //主要是一些alert、打開新窗口之類的

以下是WKNavigationDelgate的一些協議方法

//下面這2個方法共同對應了UIWebView的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//先:針對一次action來決定是否允許跳轉,action中可以獲取request,允許與否都需要調用decisionHandler,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//後:根據response來決定,是否允許跳轉,允許與否都需要調用decisionHandler,如decisionHandler(WKNavigationResponsePolicyAllow);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
//開始加載,對應UIWebView的- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//加載成功,對應UIWebView的- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
//加載失敗,對應UIWebView的- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

2、WKWebView OC調用JS

2.1 通過JavaScriptCore調用

該方法很好的解決了UIWebView使用stringByEvaluatingJavaScriptFromString:方法的兩個缺點(1. 返回值只能是NSString。2. 報錯無法捕獲)。比如說要獲取webView的title除了self.webView.title,還可以通過以下方法

-(void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id result, NSError * _Nullable error))completionHandler;
用法示例

[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) {
NSLog(@"調用evaluateJavaScript異步獲取title:%@", title);
}];

3、WKWebView JS調用OC

3.1 URL攔截方式

和UIWebView的攔截方式一致,具體可參照本文3.1,在此就不做贅述。

3.2 addScriptMessageHandler 方法

在OC中添加一個scriptMessageHandler,則會在all frames中添加一個js的function: window.webkit.messageHandlers..postMessage() ,涉及到的方法:

  • -(void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name;
  • -(void)removeScriptMessageHandlerForName:(NSString *)name;

3.3 使用示例,調用過程

第一步:在OC中註冊一個handler回調

[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"choosePhoneContact"];

第二步:js中調用方法

window.webkit.messageHandlers.choosePhoneContact.postMessage(param);

第三步:oc中實現WKScriptMessageHandler回調

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"choosePhoneContact"]) {
[self selectContactCompletion:^(NSString *name, NSString *phone) {
NSLog(@"選擇完成");
//讀取js function的字符串
NSString *jsFunctionString = message.body[@"completion"];
//拼接調用該方法的js字符串
NSString *callbackJs = [NSString stringWithFormat:@"(%@)({name: '%@', mobile: '%@'});", jsFunctionString, name, phone];
//執行回調
[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
}];
}];
}
}

第四步:調用removeScriptMessageHandler移除回調

- (void)dealloc {
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"choosePhoneContact"];
}

4、WKWebView Cookie管理

加載Cookie的時候的幾個注意事項

  • WKWebView加載網頁得到的Cookie會同步到NSHTTPCookieStorage中。
  • WKWebView加載請求時,不會同步NSHTTPCookieStorage中已有的Cookie。
  • 通過共用一個WKProcessPool並不能解決2中Cookie同步問題,且可能會造成Cookie丟失。

4.1 wkwebVire首次加載Cookie不成功的問題

在請求頭中添加cookie,這樣的話只要保證[NSHTTPCookieStorage sharedHTTPCookieStorage]中存在你的cookie,第一次請求就不會有問題了。

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.LynkCo.com"]];
NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
//Cookies數組轉換為requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//設置請求頭
request.allHTTPHeaderFields = requestHeaderFields;
[self.webView loadRequest:request];

4.2 ajax請求cookie丟失的問題

只需要通過添加WKUserScript就可以了,只要保證sharedHTTPCookieStorage中你的Cookie存在,後續Ajax請求就不會有問題。

/*!
*  更新webView的cookie
*/
- (void)updateWebViewCookie
{
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[self cookieString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
//添加Cookie
[self.configuration.userContentController addUserScript:cookieScript];
}
- (NSString *)cookieString
{
NSMutableString *script = [NSMutableString string];
[script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
// Skip cookies that will break our script
if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
continue;
}
// Create a line that appends this cookie to the web view's document's cookies
[script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.da_javascriptString];
}
return script;
}

4.3 跳轉頁面cookie丟失問題

//核心方法:
/**
修復打開鏈接Cookie丟失問題
@param request 請求
@return 一個fixedRequest
*/
- (NSURLRequest *)fixRequest:(NSURLRequest *)request
{
NSMutableURLRequest *fixedRequest;
if ([request isKindOfClass:[NSMutableURLRequest class]]) {
fixedRequest = (NSMutableURLRequest *)request;
} else {
fixedRequest = request.mutableCopy;
}
//防止Cookie丟失
NSDictionary *dict = [NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies];
if (dict.count) {
NSMutableDictionary *mDict = request.allHTTPHeaderFields.mutableCopy;
[mDict setValuesForKeysWithDictionary:dict];
fixedRequest.allHTTPHeaderFields = mDict;
}
return fixedRequest;
}
#pragma mark - WKNavigationDelegate 
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
#warning important 這裡很重要
//解決Cookie丟失問題
NSURLRequest *originalRequest = navigationAction.request;
[self fixRequest:originalRequest];
//如果originalRequest就是NSMutableURLRequest, originalRequest中已添加必要的Cookie,可以跳轉
//允許跳轉
decisionHandler(WKNavigationActionPolicyAllow);
//可能有小夥伴,會說如果originalRequest是NSURLRequest,不可變,那不就添加不了Cookie了,是的,我們不能因為這個問題,不允許跳轉,也不能在不允許跳轉之後用loadRequest加載fixedRequest,否則會出現死循環,具體的,小夥伴們可以用本地的html測試下。
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark - WKUIDelegate
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
#warning important 這裡也很重要
//這裡不打開新窗口
[self.webView loadRequest:[self fixRequest:navigationAction.request]];
return nil;
}

總結

在iOS開發中,H5的嵌入可以通過UIWebView或者WKWebView。這兩個都是繼承UIView,來加載web數據的類。UIWebView是在iOS2的時候開始使用的。特點是加載速度慢,佔用內存多,優化艱難。目前UIWebView在iOS12.0之後已被廢棄,WKWebView是在iOS8蘋果新推出的,加載速度快,佔用內存較少,是一個不錯的選擇。

但是目前WKWebView依然存在很多坑,比如

  • 當WKWebView加載的網頁佔用內存過大時,會出現白屏現象
  • 如果不實現WKUIDelegate中的代理方法,js alert方法就不出現彈窗。所以還要實現這個方法

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

  • Cookie丟失,比如從一個登錄狀態的頁面跳轉到另一個頁面cookie就會丟失。
  • evaluateJavaScript:completionHandler:只能通過異步回調的方式來實現
  • 自定義contentInset頁面刷新時會出現頁面跳動的問題,比如self.webView.scrollView.contentInset = UIEdgeInsetsMake(64, 0, 44, 0);可以通過kvc給私有變量_obscuredInsets設置值,

[self.webView setValue:[NSValue valueWithUIEdgeInsets:self.webView.scrollView.contentInset] forKey:@”_obscuredInsets”]。

  • 不支持攔截NSURLProtocol。如果要攔截可以使用UIWebView

DEMO地址

相關文章

iOSRunLoop的使用及底層原理

iOSload和initialize的異同

iOS性能優化(初級)

IMYAOPTableView源碼學習筆記