WKWebview:JS交互、Cookie的注入与清除

一. WKWebView与JS交互

首先使用WKWebView.你需要导入WebKit。关于WKWebView其他基础使用不在本篇研究范围。

1. iOS端执行一段js代码。

使用

1
2
3
4
5
```
//OC代码
[webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable response, NSError * _Nullable error) {

}];

1
2
3
4
//swift代码
webview.evaluateJavaScript(jsStr) { (response, error) in

}

一般使用以上方法,是在网页加载完成(

1
2
###### 2. 通过使用userContentController向网页注入js。
注入的js可以取名字,将会在```WKScriptMessageHandler```的代理方法```didReceiveScriptMessage```中被回掉。

//OC代码
NSString js = @”I am JS Code”;
//初始化WKUserScript对象
//WKUserScriptInjectionTimeAtDocumentEnd为网页加载完成时注入
WKUserScript
script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
//根据生成的WKUserScript对象,初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
//设置ScriptMessageHandler为self
[config.userContentController addScriptMessageHandler:self name:@”APPJS”];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```
//swift代码
//从js文件加载js代码
let path = (bundlePath) + ("/" + "Contents/Resources/ContextMenu.js")
let source = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) as String

let path2 = (bundlePath) + ("/" + "Contents/Resources/JSBridge.js")
let source2 = try! NSString(contentsOfFile: path2, encoding: String.Encoding.utf8.rawValue) as String
let js = source + source2

let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
configuration!.userContentController.addUserScript(userScript)
//设置ScriptMessageHandler为self
configuration.userContentController.add(TabManager.sharedInstance, name: "APPJS")
let newWebView = WKWebView(frame: CGRect.zero, configuration: configuration)
self.webView = newWebView

3. 在网页里,使用js代码进行与原生交互。

比如js需要app已经登陆了用户,js调用原生的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//js代码
function callLogin() {
// APPJS是我们所注入的对象
window.webkit.messageHandlers.APPJS.postMessage("shouldLogin");
}

//复杂点的js方法,参数中约定好格式。
//比如:fun代表方法名,arg代表参数,callback是原生处理完需要回掉js的方法名
function callLogin() {
// APPJS是我们所注入的对象
window.webkit.messageHandlers.APPJS.postMessage({
fun: 'notifyLoginOut',
arg: {
callback: ""
}
});
}

4. 原生处理js内容。

通过在

1
2
3
4
5
6
7
8
9
10
11
12
13
```
//OC代码
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"APPJS"]) {
// 打印所传过来的参数,只支持NSNumber, NSString, NSDate, NSArray,
// NSDictionary, and NSNull类型
//do something
NSLog(@"%@", message.body);
}else if ([message.name isEqualToString:@"AppModel"]){
NSLog(@"%@", message.body);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//swift代码,这个对应上面比较复杂js的处理,按约定格式
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
if message.name == "APPJS" {
if let dic = message.body as? NSDictionary, dic["fun"] != nil,
let fun = (dic["fun"] as AnyObject).description{
// print("dic: \(dic)")
if let arg = dic["arg"] as? NSArray {
if fun == "homeList" && arg.count == 1{
self.homeList(arg[0] as? String ?? "")
}

else if fun == "homeDelete" && arg.count == 1{
self.homeDelete(arg[0] as? String ?? "")
}
}
}
}
5. 原生调用js方法。

上面的这些足够应付大多数的js和原生交互了。
当然,你还有一个使用场景,想要原生某些控件去调用网页里的某个js方法。比如:点击toolBar上的目录按钮,让网页左侧显示出章节目录。
已知js的显示章节目录的js方法名为

1
2


//OC代码
NSString jsStr = @”window.toggleCatalog();”;
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable, NSError
_Nullable error) {

}]

1
2
3
4
```
//swift代码
let js ="window.toggleCatalog();"
webView?.evaluateJavaScript(js, completionHandler: nil)

二. WKWebView的cookie注入与清除

WKWebView与UIWebview的一个区别,就是WKWebView实例将会忽略任何的默认网络存储器(NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) 和一些标准的自定义网络请求类(NSURLProtocol,等等.).
WKWebView实例不会把Cookie存入到App标准的的Cookie容器(NSHTTPCookieStorage)中,因为 NSURLSession/NSURLConnection等网络请求使用NSHTTPCookieStorage进行访问Cookie,所以不能访问WKWebView的Cookie,现象就是WKWebView存了Cookie,其他的网络类如NSURLSession/NSURLConnection却看不到
与Cookie相同的情况就是WKWebView的缓存,凭据等。WKWebView都拥有自己的私有存储,因此和标准cocoa网络类兼容的不是那么好

NSHTTPCookieStorage 实现管理cookie的单利,每个cookie都是NSHTTPCookie类的实例,做为一个规则,cookie在所有应用 之间共享并在不同进程之间保持同步。
上面引入了网页需要用户登陆,然后让app跳转登陆界面进行登陆,app登陆之后自然要向网页注入cookie,来让网页继续剩下的功能。

1. 在webview发起请求的时候附带cookie。

这个适用首次发起网页请求,同样适用点击,在webview代理方法里,判断是否需要注入cookie的域名,如果是,截断请求,重新发起注入了cookie的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//oc代码
NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieJar cookies]) {
[cookieDic setObject:cookie.value forKey:cookie.name];
}

// cookie重复,先放到字典进行去重,再进行拼接
for (NSString *key in cookieDic) {
NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
[cookieValue appendString:appendString];
}
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
[request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
NSLog(@"添加cookie");
[self.webView loadRequest:request];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//swift代码
guard let cookies = HTTPCookieStorage.shared.cookies else {
return
}
var cookieDic = Dictionary<String, Any>()
var cookieValue = ""
for cookie in cookies{
cookieDic[cookie.name] = cookie.value
}
for (key,value) in cookieDic {
let appendString = "\(key)=\(value)"
cookieValue.append(appendString)
}
let request = URLRequest.init(url: URL.init(string: "url")!)
request.addValue(cookieValue, forHTTPHeaderField: "Cookie")
2. 在webview创建的时候js注入cookie。

其中js的写法问题,有可能有多个写法是cookie之间用

1
2


//OC代码
WKUserContentController* userContentController = WKUserContentController.new;

WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @”document.cookie =’TeskCookieKey1=TeskCookieValue1’;document.cookie = ‘TeskCookieKey2=TeskCookieValue2’;”injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];

WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;

webViewConfig.userContentController = userContentController;

WKWebView webView = [[WKWebView alloc] initWithFrame:CGRectMake(/set your values*/) configuration:webViewConfig];

1
2
3
4
5
6
7
8
9
10
11
```
//swift代码
let userContent = WKUserContentController()
let jsStr = "document.cookie ='TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"

let cookieScript = WKUserScript.init(source: jsStr, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
userContent.addUserScript(cookieScript)
let webViewConfig = WKWebViewConfiguration()
webViewConfig.userContentController = userContent

let webview = WKWebView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 300), configuration: webViewConfig)

3. 在webview加载内容时js注入cookie。
1
2
3
4
5
6
7
8
9
//swift代码
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
if let laToken = UserCenter.shared().user?.laToken {
let cookie = "115token=\(oofToken)"
webView?.evaluateJavaScript("function setCookie(e,o){document.cookie=e+\"=\"+escape(o)+\";path=/;domain=.115.com\"}for(var cookieTem= \"\(cookie)\",cookieArr=cookieTem.split(\";\"),i=0;i<cookieArr.length;i++){var temArr=cookieArr[i].split(\"=\");setCookie(temArr[0],temArr[1])}", completionHandler: {
(object, error) -> Void in
})
}
}

经过验证,最好是第一点和第三点同时使用,第二点每次截断请求总觉得浪费资源 -。-

4. 清理注入的cookie。

当用户退出登陆的时候,需要清理已经注入的cookie。在iOS9之前,wkwebview是没有清理cookie的方法的,所以需要对不同的版本进行不同的操作。
那iOS9之前的如何操作?可以预见,既然是缓存,肯定是放在沙盒里的。找到沙盒的目录,删除文件即可。
```
//swift代码
/// 清理cookie缓存数据
func ClearCache() {

if #available(iOS 9.0, *) {
    let websiteDataTypes = WKWebsiteDataStore.allWebsiteDataTypes()
    let dateFrom = Date.init(timeIntervalSince1970: 0)

// NSDate.init(timeIntervalSince1970: 0)
// let websiteDataTypes: NSSet = WKWebsiteDataStore.allWebsiteDataTypes()
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom, completionHandler: {
//done
})

} else {

    let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0]
    let cookiesFolderPath = libraryPath+"/Cookies"
    try? FileManager.default.removeItem(atPath: cookiesFolderPath)

// try? NSFileManager.defaultManager().removeItemAtPath(cookiesFolderPath)
}
}