Swift:WKWebView 进阶填坑指南
Posted 程序员大咖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift:WKWebView 进阶填坑指南相关的知识,希望对你有一定的参考价值。
????????关注后回复 “进群” ,拉你进程序员交流群????????
作者丨辰牧殇
转自丨掘金
https://juejin.cn/post/6953427503507570701
Cookie处理
在设置Cookie
的时候,我们经常做的是在请求的请求头里添加 Cookie
,但是这只是把Cookie
发送给了服务端,我们本地并没有保存 Cookie
,Cookie
最终要写到WebView
的一个Cookie
文件目录里面,后续WebView
里面自己的发起的请求或者跳转才能在发起请求的时候在对应的域名下面取到Cookie
传出去。
Webview
加载 H5 页面,实际上是把页面相关的.html、js、css
文件下载到本地,然后再加载,这时页面去获取 Cookie
的时候,是去本地WebView
里的Cookie
文件目录里查找,如果没有设置的话肯定就找不到了。所以在设置Cookie
的时候,服务端和客户端都要设置。
一、服务端 Cookie 设置
在使用UIWebView
的时候,我们是通过 NSHTTPCookieStorage
来管理 Cookie
的,下面我们给devqiaoyu.tech
这个域名添加一个名为user
的Cookie
。
var props = Dictionary<HTTPCookiePropertyKey, Any>()
props[HTTPCookiePropertyKey.name] = "user"
props[HTTPCookiePropertyKey.value] = "admin"
props[HTTPCookiePropertyKey.path] = "/"
props[HTTPCookiePropertyKey.domain] = "devqiaoyu.tech"
props[HTTPCookiePropertyKey.version] = "0"
props[HTTPCookiePropertyKey.originURL] = "devqiaoyu.tech"
if let cookie = HTTPCookie(properties: props) {
HTTPCookieStorage.shared.setCookie(cookie)
}
WKWebView Cookie
问题在于WKWebView
发起的请求不会自动带上存储于NSHTTPCookieStorage
容器中的Cookie
。
解决办法也很简单,就是在WKWebView
发起请求之前,先从NSHTTPCookieStorage
读取Cookie
,然后手动往URLRequest
的请求头里添加一下Cookie
。
func getCookie() -> String {
var cookieString = ""
if let cookies = HTTPCookieStorage.shared.cookies {
for cookie in cookies {
if cookie.domain == cookieDomain {
let str = "\\(cookie.name)=\\(cookie.value)"
cookieString.append("\\(str);")
}
}
return cookieString
}
var request = URLRequest(url: URL(string: "https://devqiaoyu.com"))
request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
当服务器页面发生重定向的时候,此时第一次在RequestHeader
中写入的Cookie
会丢失,还需要对重定向的请求重新做添加Cookie
的处理。具体方法请往下看~
二、客户端 Cookie 设置
上面这么写完了,当页面加载的时候,后端无论是啥语言,都能从请求头里看到Cookie
了,但是后端渲染返回页面后,在客户端的WebView
里运行的时候,JS 在执行的时候调用 document.cookie
API 是读取不到Cookie
的,所以还得针对客户端Cookie
进行处理。
var cookieString = ""
if let cookies = HTTPCookieStorage.shared.cookies {
for cookie in cookies {
if cookie.domain == "devqiaoyu.tech" {
let str = "\\(cookie.name)=\\(cookie.value)"
cookieString.append("document.cookie='\\(str);path=/;domain=devqiaoyu.tech';")
}
}
}
let cookieScript = WKUserScript(source: cookieString, injectionTime: .atDocumentStart, forMainFrameOnly: false)
let userContentController = WKUserContentController()
userContentController.addUserScript(cookieScript)
let webViewConfig = WKWebViewConfiguration()
webViewConfig.userContentController = userContentController
let webV = WKWebView(frame: CGRect.zero, configuration: webViewConfig)
**客户端Cookie
注入实际上就是创建一个 JS 脚本,让WebView
去执行,推荐在.atDocumentStart
这个时机进行预置静态 JS 的注入。**这样WebView
在加载后端返回的静态页面的时候,就可以拿到保存着客户端的Cookie
了。
注意:document.cookie() 无法跨域设置 Cookie,比如你第一次加载的请求时 www.baidu.com ,在重定向的时候跳转到了 www.google.com ,那么第二个请求就可能因为没有携带
Cookie
而无法访问。当然啦,解决办法还是有的,请往下看~
URL拦截
在WKWebView
中,每一次页面跳转之前,都会调用下面的回调函数:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
Web 页面重定向问题
重定向问题有两种:
服务器页面重定向,需要对新发起的请求重新种
Cookie
本地页面重定向,只要客户端设置了
Cookie
,那么就不需要处理了
所以如果是服务器页面重定向,那么判断此时Request
是否有你要的Cookie
没有就Cancel
掉,修改Request
重新发起。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
{
var shouldCancelLoadURL = false
if let cookie = navigationAction.request.value(forHTTPHeaderField: "Cookie") {
if cookie.contains("user") {
shouldCancelLoadURL = false
} else {
var request = URLRequest(url: URL(string: (navigationAction.request.url?.absoluteString)!)!)
request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
webView.load(request)
shouldCancelLoadURL = true
}
} else {
var request = URLRequest(url: URL(string: (navigationAction.request.url?.absoluteString)!)!)
request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
webView.load(request)
shouldCancelLoadURL = true
}
if shouldCancelLoadURL {
decisionHandler(WKNavigationActionPolicy.cancel)
} else {
decisionHandler(WKNavigationActionPolicy.allow)
}
}
跨域问题
针对跨域的问题,解决办法和上面的方法类似,仅仅是判断条件不同。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
{
var shouldCancelLoadURL = false
if let url = navigationAction.request.url?.absoluteString {
if url.contains("devqiaoyu.tech") { // 原来的域名
shouldCancelLoadURL = false
} else {
// 重新发起请求,种Cookie
shouldCancelLoadURL = true
}
} else {
// 重新发起请求,种Cookie
shouldCancelLoadURL = true
}
if shouldCancelLoadURL {
decisionHandler(WKNavigationActionPolicy.cancel)
} else {
decisionHandler(WKNavigationActionPolicy.allow)
}
}
假跳转的请求拦截
一种 JS 调用 Native 的通信方案,详细介绍可以看从零收拾一个hybrid框架(一)-- 从选择JS通信方案开始。下面内容是从该文章内摘录的。
何谓 假跳转的请求拦截 就是由网页发出一条新的跳转请求,跳转的目的地是一个非法的压根就不存在的地址,比如
//常规的Http地址
https://wenku.baidu.com/xxxx?xx=xx
//假的请求通信地址
wakaka://wahahalalala/action?param=paramobj
看我下面写的那条假跳转地址,这么一条什么都不是的扯淡地址,直接放到浏览器里,直接扔到WebView
里,肯定是妥妥的什么都打不开的,而如果在经过我们改造过的Hybrid WebView
里,进行拦截不进行跳转
url 地址分为这么几个部分
协议:也就是
http/https/file
等,上面用了wakaka
域名:上面的
wenku.baidu.com
或wahahalalala
路径:上面的
xxxx?
或action?
参数:上面的
xx=xx
或param=paramobj
如果我们构建一条假url
用协议与域名当做通信识别
用路径当做指令识别
用参数当做数据传递
客户端会无差别拦截所有请求,真正的 url 地址应该照常放过,只有协议域名匹配的 url 地址才应该被客户端拦截,拦截下来的 url 不会导致 WebView 继续跳转错误地址,因此无感知,相反拦截下来的 url 我们可以读取其中路径当做指令,读取其中参数当做数据,从而根据约定调用对应的 Native 原生代码
以上其实是一种 协议约定 只要 JS 侧按着这个约定协议生成假 url,Native 按着约定协议拦截/读取假 url,整个流程就能跑通。
User-Agent设置
全局设置
就是App
内所有Web
请求的User-Agent
全部被修改。
// UIWebView
let webView = UIWebView(frame: CGRect.zero)
let userAgent = webView.stringByEvaluatingjavascript(from: "navigator.userAgent")
if let agent = userAgent {
let user = "@\\(agent);extra_user_agent"
let dict = ["UserAgent":user]
UserDefaults.standard.register(defaults: dict)
}
// WKWebView
let webV = WKWebView(frame: CGRect.zero)
webV.evaluateJavaScript("navigator.userAgent") { (result, error) in
if let oldAgent = result as? String {
let user = "@\\(oldAgent);extra_user_agent"
let dict = ["UserAgent":user]
UserDefaults.standard.register(defaults: dict)
}
}
单个WebView设置
在ios9
,WKWebView
提供了一个非常便捷的属性去更改User-Agent
,就是customUserAgent
属性。这样使用起来不仅方便,也不会全局更改User-Agent
,可惜的是iOS9
才有,如果适配iOS8
,还是要使用上面的方法。
let webView = UIWebView(frame: CGRect.zero)
let userAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent")
if let agent = userAgent {
let user = "@\\(agent);extra_user_agent"
webView.customUserAgent = user
}
参考文章
WKWebView 那些坑
转自:掘金 辰牧殇
https://juejin.cn/post/6953427503507570701
-End-
最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击????卡片,关注后回复【面试题
】即可获取
在看点这里好文分享给更多人↓↓
以上是关于Swift:WKWebView 进阶填坑指南的主要内容,如果未能解决你的问题,请参考以下文章
《算法竞赛进阶指南》 第二章 Acwing 137. 雪花雪花雪花 哈希