踩坑WKWebView

2019-03-28  本文已影响0人  冬冬吧

背景

在iOS应用开发中,内嵌WebView一直占有一定的页面数量比例。它能以较低的开发成本实现iOS、Android和Web的复用,也可以一定程度的的规避苹果对热更新的封锁。然而UIWebView的CPU资源消耗和内存占用一直被嫌弃,导致很多客户端中需要动态更新等页面时不得不采用其他方案。长远来看,功能的动态加载以及三端的融合将会是大趋势。怎么解决WebView固有的问题呢?我们将通过全面的对比来分析使用UIWebView的问题。

全面对比

private func configWebView() -> WKWebView {
    let webConfig = WKWebViewConfiguration()
    
    // 1.删除沙盒中 之前旧版本的cookies以及现在的cookie  2.实时从登录信息中组合cookies,不以沙盒中的cookie为准
    if #available(iOS 11.0, *) {
        /// iOS 11以上
        let webView = WKWebView.init(frame: .zero, configuration: webConfig)
        let store = webConfig.websiteDataStore.httpCookieStore
        store.getAllCookies { (items) in
            for item in items {
                if let comment = item.comment, comment.contains("fc_") {
                    FCLog("开始删除:\(item.domain)____\(item.name)")
                    store.delete(item, completionHandler: {
                        FCLog("\(item.domain) 删除了")
                    })
                }
            }
            for item in HttpCookieManager.cookies {
                store.setCookie(item, completionHandler: nil)
            }
        }
        return webView
    } else {
        // iOS 11以下
        let userContentController = WKUserContentController()
        webConfig.userContentController = userContentController
        for str in HttpCookieManager.getAllCookies() {
            let setCookie = "document.cookie='\(str)';"
            let cookieScript = WKUserScript.init(source: setCookie, injectionTime: .atDocumentStart, forMainFrameOnly: false)
            userContentController.addUserScript(cookieScript)
        }
        let webView = WKWebView.init(frame: .zero, configuration: webConfig)
        return webView
    }
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
    // 监听 WKWebView 对象的 estimatedProgress 属性,就是当前网页加载的进度
    webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
    
    // 监听 WKWebView 对象的 title 属性,就是当前网页title
    webView.addObserver(self, forKeyPath: "title", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let x = change?[.newKey], let keyPath = keyPath {
        switch keyPath {
        case "estimatedProgress":
            if let progress = x as? Float {
                print("progress is \(progress)")
                progressView.setProgress(progress, animated: true)
            }
        case "title":
            if let title = x as? String, let action = didGetTitleAction {
                action(title)
            }
        default:
            print("observeValue:\(x)")
        }
    }
}

业务场景

native与JS的相互调用

WKWebView对于HTML5的操作已经很便捷了,但是还没有Android的WebView那样简单。WebView能够直接注入JavaScript对象,交互过程中Java 与 JavaScript甚至可以直接调用对方的方法,不用拦截,不用分发,这样的Java 与 JavaScript的交互非常清晰明了。在iOS上,还达不到这样的便捷。
在使用WKWebView时,H5调用Native 的过程是:1、Native注入JavaScript函数;2、Native实现桥接方法:通过系统方法拦截JavaScript事件,匹配OC/Swift注册列表,分发调用不同的原生方法。附上代码:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
        decisionHandler(.allow)
        return
    }
    guard let urlString = url.absoluteString.removingPercentEncoding else {
        decisionHandler(.allow)
        return
    }
    // 现有业务协议,与H5约定命名,可携带参数
    guard urlString.contains("JS协议"),
        let preRange = urlString.range(of: "JS协议") else {
            decisionHandler(.allow)
            return
    }
    
    let jsonStr = urlString[preRange.upperBound..<urlString.endIndex]
    guard let jsonData = jsonStr.data(using: .utf8) else {
        decisionHandler(.allow)
        return
    }
    let json = JSON(jsonData)
    guard !json.isEmpty else {
        decisionHandler(.allow)
        return
    }
    decisionHandler(.allow)
}

而OC/Swift调用JavaScript的过程是:使用WKWebView的接口调用JavaScript函数。附上代码

self.webView.evaluateJavaScript(javaScript, completionHandler: { (_, error) in
    if let error = error {
        FCLog("webJS——\(javaScript)执行失败: \(error.localizedDescription)")
    }
})

动态加载并运行JS代码

附上示例代码

// js代码片段
let jsStr = "var clickBtn = document.getElementsByClassName('click_fcbox');for(var j = 0;j < clickBtn.length; j++){clickBtn[j].onclick = function(){this.removeAttribute('clicked');}}"

// 根据JS字符串初始化WKUserScript对象
let testScript = WKUserScript(source: jsStr, injectionTime:.atDocumentEnd, forMainFrameOnly: true)
let testController = WKUserContentController()
testController.addUserScript(testScript)

// 根据生成的WKUserScript对象,初始化WKWebViewConfiguration
let webConfiguration = WKWebViewConfiguration()
webConfiguration.userContentController = testController
let testWebview = WKWebView(frame: CGRect.zero, configuration: webConfiguration)
view.addSubview(testWebview)

踩坑实录

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
       webView.load(navigationAction.request)
       return nil
   }
if !(urlString.hasPrefix("http://") || urlString.hasPrefix("https://")) {
    if UIApplication.shared.canOpenURL(appUrl) {
        UIApplication.shared.openURL(appUrl)
    }
}
let request = NSMutableURLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
上一篇 下一篇

猜你喜欢

热点阅读