美文网首页IOS
[iOS]使用WKWebView遇到的问题总结

[iOS]使用WKWebView遇到的问题总结

作者: 流火绯瞳 | 来源:发表于2017-11-29 08:49 被阅读2654次

    一. 获取UserAgent为nil

    在使用WKWebView获取userAgent的时候, 如果要全局配置, 使所有的WKWebView都能生效, 我们可能的做法是在AppDelegate中来配置, 但是需要一个WKWebView实例对象, 所以可能是这么写的:

    let webView = WKWebView()
    webView.evaluateJavaScript("navigator.userAgent") { (info, error) in
                // 获取默认值
                if var userAgent = info as? String {
                    // 添加自定义的内容
                    if userAgent.hasSuffix("/ios-app") == false {
                        userAgent += "/ios-app"
                    }
                    // 设置global User-Agent
                    let dic = ["UserAgent": userAgent]
                    UserDefaults.standard.register(defaults: dic)
                    UserDefaults.standard.synchronize()
                }
            }
    

    这样, 你会发现设置一直是无效的, 在回调里打印一下:

    print(info)
    print(error)
    

    这时发现获取到的 info 字段为nil, 并且error信息如下:

    Error Domain=WKErrorDomain Code=3 "WKWebView 已失效" UserInfo={NSLocalizedDescription=WKWebView 已失效}
    

    个人猜测: 这是因为, WKWebView的evaluateJavaScript方法是异步执行的, 当WKWebView回调这个方法的时候, 其实例对象已经从内存中释放了, 所以导致回调出错.
    我做了如下验证, 在回调方法里输出webView实例对象:

    let webView = WKWebView()
    webView.evaluateJavaScript("navigator.userAgent") { (info, error) in
    
              print(info)
              print(error)
              print(webView)
                // 获取默认值
                if var userAgent = info as? String {
                    // 添加自定义的内容
                    if userAgent.hasSuffix("/ios-app") == false {
                        userAgent += "/ios-app"
                    }
                    // 设置global User-Agent
                    let dic = ["UserAgent": userAgent]
                    UserDefaults.standard.register(defaults: dic)
                    UserDefaults.standard.synchronize()
                }
            }
    

    会发现输出是info有值, 而error为nil, webView有值, 又正常了, 有人说了,看样子不是这个问题! 真的么? 仔细想一下会发现, 在webView的方法回调闭包里使用了webView实例, 会发生什么? 对, 循环引用! webView实例此时不为nil, 这也验证了, 如果webView实例正常的话, 获取结果是不会有误的! 继续上面的验证, 我们弱引用一下:

    let webView = WKWebView()
    webView.evaluateJavaScript("navigator.userAgent") {[weak webView] (info, error) in
    
              print(info)
              print(error)
              print(webView)
                // 获取默认值
                if var userAgent = info as? String {
                    // 添加自定义的内容
                    if userAgent.hasSuffix("/ios-app") == false {
                        userAgent += "/ios-app"
                    }
                    // 设置global User-Agent
                    let dic = ["UserAgent": userAgent]
                    UserDefaults.standard.register(defaults: dic)
                    UserDefaults.standard.synchronize()
                }
            }
    

    这时, 会发现: info和webView都为nil, error值为上面那个错误!!!

    这样, 基本验证出现这个问题的原因是: webView 提前释放了!

    但是为了添加这个设置, 而将webView 设为全局变量, 仿佛有点得不偿失, 这时可以在使用webView的页面进行设置, 或者使用UIWebView替换:

    // 获取默认值
            if let oldAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent") {
                var newAgent = oldAgent
    
                // 添加自定义的内容
                if oldAgent.hasSuffix("/artron-cgyc") == false {
                    newAgent += "/artron-cgyc"
                }
    
                // 设置global User-Agent
                let dic = ["UserAgent": newAgent]
                UserDefaults.standard.register(defaults: dic)
                UserDefaults.standard.synchronize()
            }
    

    二. 无需传参时注入的交互协议无效

    在做JS与原生交互的时候, 使用下面方法注入的协议无效:

     let user = WKUserContentController()
     // 向js中注入协议, 作为ios和js交互的依据
     user.add(self, name: "appProtocol")
    

    然后在js端使用的时候: 这里不需要传参数, 直接这么写的

    window.webkit.messageHandlers.appProtocol.postMessage();
    

    这样, 没有响应js端的事件!!!
    在代理方法中:

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            
            if message.name == "appProtocol" {
                print(message.body)
            }
        }
    

    一直没有收到回调!!!
    其实, 并不是注入协议失败, 这么使用也没问题, 问题就出在postMessage的参数上, 如果是带参数的:

    window.webkit.messageHandlers.appProtocol.postMessage({info: 'info', num: '123456788', price: '100'});
    

    这么写, 是完全没有问题的, 所以如果不需传参数的话, 可以这么写:

    // 无参数使用
    window.webkit.messageHandlers.appProtocol.postMessage({});
    

    给一个空的字典, 就能正常交互了!!!

    三. 屏蔽页面长按手势

    在WKWebView加载的HTML页面上, 如果长按会弹出一些选择框, 在文字上长按, 会弹出UIMenuController选择框:

    长按文字

    而在图片上长按, 会弹出一个alertSheet:

    屏幕快照 2017-11-30 下午3.20.31.png

    这里可以保存图片到系统相册(如果有权限), 或者复制到剪切板. 但是这些需求并不是我们需要, 如何禁止这些行为呢?需要从JS入手, 只需要执行下面两句js即可:

    // 禁止图片长按事件
    document.documentElement.style.webkitTouchCallout='none';
    //禁止文本长按事件
    document.documentElement.style.webkitUserSelect='none';
    

    可以在创建WKWebView的时候注入:

    let config = WKWebViewConfiguration()
    config.userContentController = WKUserContentController()
    let jsStr = """
        document.documentElement.style.webkitTouchCallout='none';
        document.documentElement.style.webkitUserSelect='none';
              """
    let noneSelectJS = WKUserScript(source: jsStr, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
    config.userContentController.addUserScript(noneSelectJS)
    let web = WKWebView(frame: .zero, configuration: config)
    

    也可以在页面加载完成后的代理方法中执行:

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    
            // 结束加载
            let jsStr = """
                        document.documentElement.style.webkitTouchCallout='none';
                        document.documentElement.style.webkitUserSelect='none';
                    """
            webView.evaluateJavaScript(jsStr) { (info, error) in
                
            }
        }
    

    四. 页面中出现第三方的广告悬浮框(Ta)

    在加载的HTML页面中, 无端出现一个广告的悬浮框:

    第三方广告悬浮框

    打开之后是这样的:

    第三方广告页面

    而且只会在移动4G网络下才会出现, 其实这是移动的流量劫持, 强加的广告推广,目前网上有一些解决方式,常用的有:

    1. 使用IP地址访问:将域名改为IP地址访问数据;
    2. 使用HTTPS;
    3. 前端的小伙伴想想办法

    其他的可参考这篇文章 iOS 客户端对于运营商劫持的一点点对抗方式

    五. 显示HTML页面不是最新的内容

    在联调的时候, 前端的同学改了一些东西, 例如页面的布局, 显示元素, 或者js方法, 而APP端没反应!!!

    这是因为, WKWebView有缓存, 为了保证每次加载的都是最新的页面, 可以在加载的链接后面加上一个时间戳, 例如你的HTML地址为:

    http://your host name/test/20171127.html
    

    一般使用是这样的:

    let urlStr = "http://your host name/test/20171127.html"
    
          if let url = URL(string: urlStr) {
                let request = URLRequest(url: url)
                webView.load(request)
    }
    

    这样的话是有缓存, 加载一次之后, 再去加载也不是最新的页面, 可以这样使用:

    let time = Date().timeIntervalSince1970   
    let urlStr = "http://your host name/test/20171127.html?_t=\(time)"
                    
            if let url = URL(string: urlStr) {
                let request = URLRequest(url: url)
                webView.load(request)
            }
    

    这样每次加载的时候都会是最新的, 当然弊端就是, 每次都会耗费一些额外的流量.

    相关文章

      网友评论

        本文标题:[iOS]使用WKWebView遇到的问题总结

        本文链接:https://www.haomeiwen.com/subject/urjebxtx.html