前因
由于众所周知的原因,UIWebView 要离开历史的舞台了。于是在最新的版本中更改 UIWebView 为 WKWebView, 现在上线了,简单做一个记录。
问题
接入 WKWebView 很简单,但是会出现以下问题
UserAgent 问题
UIWebView 的 UserAgent 可以统一设置,但是 WKWebView 不行。WKWebView 提供了单独的属性 customUserAgent 用来设置 UserAgent。
首先在 didFinishLaunchingWithOptions 方法中获取 UA
注意:WKWebView 的 evaluateJavaScript 是异步回调,所以需要把 webView 声明为属性
1 2 3 4 5 6 7 8 9 10
| func setWebviewUserAgent() { webView?.evaluateJavaScript("navigator.userAgent", completionHandler: { (object, error) -> Void in if let defaultUserAgent = object as? String { let newUserAgent = defaultUserAgent + UserAgent.userAgentString UserDefaults.standard.set(newUserAgent, forKey: "UserAgent") UserDefaults.standard.synchronize() } }) }
|
然后再设置给 WKWebView
1 2 3
| if let ua = UserDefaults.standard.string(forKey: "UserAgent") { webView.customUserAgent = ua }
|
Cookie 问题
App 一直是自动管理 cookie 的(用户登录成功后,cookie 会自动保存到 HTTPCookieStorage 中),UIWebView 也会自动从 HTTPCookieStorage 中读取 cookie,但是 WKWebView 不会,这就需要我们手动管理 cookie 了。
创建 CookieManager 单例类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class CookieManager { static let shared = CookieManager() fileprivate init(){} fileprivate let CookieStoreKey = "com.xxx.app.Cookie" var cookieString: String? { return UserDefaults.standard.string(forKey: CookieStoreKey) } fileprivate func setNewCookie() { if let cookies = HTTPCookieStorage.shared.cookies { var cookieString = "" for cookie in cookies { cookieString += "\(cookie.name)=\(cookie.value);" } UserDefaults.standard.set(cookieString, forKey: CookieStoreKey) UserDefaults.standard.synchronize() } } fileprivate func deleteCookie() { UserDefaults.standard.set(nil, forKey: CookieStoreKey) UserDefaults.standard.synchronize() let storage = HTTPCookieStorage.shared if let cookies = storage.cookies { for cookie in cookies { storage.deleteCookie(cookie) } } } func registerCookies() { if let _ = CurrentUser { if cookieString == nil { setNewCookie() } } else { deleteCookie() } } func updateCookie() { if let _ = CurrentUser { setNewCookie() NotificationCenter.default.post(name: .updateWebviewCookieNotification, object: nil) } else { deleteCookie() } } }
|
然后设置给 WKWebView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| fileprivate func setupWebview() { let wkUController = WKUserContentController() setCookieByJS(wkUController)
let config = WKWebViewConfiguration() config.userContentController = wkUController
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight), configuration: config) view.addSubview(webView) NotificationCenter.default.addObserver(self, selector: #selector(self.updateCookieAction), name: .updateWebviewCookieNotification, object: nil) }
fileprivate func loadRequest() { guard let url = url else { return } let request = NSMutableURLRequest(url: url) self.request = request
setCookie(request) webView.load(request as URLRequest) }
@objc func updateCookieAction() { let wkUController = webView.configuration.userContentController setCookieByJS(wkUController)
if let request = request { setCookie(request) } }
func setCookie(_ request: NSMutableURLRequest) { request.addValue("xxx", forHTTPHeaderField: "Cookie") } func setCookieByJS(_ userController: WKUserContentController) { let cookieScript = WKUserScript(source: "xxx", injectionTime: .atDocumentStart, forMainFrameOnly: false) userController.addUserScript(cookieScript) }
|
WKWebView 白屏问题
当 WKWebView 内存占用过大的时候,会发生白屏现象,需要重新加载页面
1 2 3
| func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { webView.reload() }
|
JS 的 alert()、confirm()、prompt()方法无法正常执行 问题
由于安全机制的问题,WKWebView 默认对 JavaScript 下 alert 类的方法(包括alert()、confirm()、prompt())做了拦截,如果要想正常使用,需要实现 WKWebView 的三个代理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let action = UIAlertAction(title: "确认", style: .default) { (action) in completionHandler() } UIViewController.topMost?.showAlert("提示", message: message, alertActions: [action]) } func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let action = UIAlertAction(title: "确认", style: .default) { (action) in completionHandler(true) } let action2 = UIAlertAction(title: "取消", style: .cancel) { (action) in completionHandler(false) } UIViewController.topMost?.showAlert("提示", message: message, alertActions: [action, action2]) } func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alertController = UIAlertController(title: prompt, message: "", preferredStyle: UIAlertController.Style.alert) alertController.addTextField { (textField) in textField.text = defaultText } let action = UIAlertAction(title: "完成", style: .default) { (action) in completionHandler(alertController.textFields?.first?.text) } alertController.addAction(action) UIViewController.topMost?.present(alertController, animated: true, completion: nil) }
|
点击H5页面文本输入框页面放大 问题
当文本输入框太小时(好像是低于17px)会出现这类问题,页面太多,前端不太方便处理时,可以在App中这样处理
1 2 3 4 5 6 7
| func scrollViewDidZoom(_ scrollView: UIScrollView) { webView.scrollView.setZoomScale(0, animated: false) }
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { webView.scrollView.setZoomScale(0, animated: false) }
|