WKWebView 的一些小总结

WKWebView是  在iOS 8后推出要替代UIWebView。相对于成熟的UIWebView来说,这个后生仔在使用上仍是有点点小坑的~javascript

p1

使用

在初始化上,WKWebViewUIWebView 没有多大的差别。前端

// WKWebView
let wkWeb = WKWebView(frame: view.bounds)
// 一些代理
wkWeb.navigationDelegate = self
wkWeb.uiDelegate = self

// UIWebView
let web = UIWebView(frame: view.bounds)
// 一些代理
web.delegate = self

两者在初始化上仍是蛮像的。一个图样的我。(逃java

仔细翻开了WKWebView,发现其还提供一个初始化方法。git

public init(frame: CGRect, configuration: WKWebViewConfiguration)github

也就是能够用WKWebViewConfigurationinit一个WKWebViewweb

后面再继续港 WKWebViewConfigurationjson

那几个协议

WKNavigationDelegate

WKNavigationDelegate的协议方法仍是挺多的。swift

// 1)接受网页信息,决定是否加载仍是取消。必须执行肥调 decisionHandler 。逃逸闭包的属性
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    print("\(#function)")
}

// 2) 开始加载
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print("\(#function)")
}

// 3) 接受到网页 response 后, 能够根据 statusCode 决定是否 继续加载。allow or cancel, 必须执行肥调 decisionHandler 。逃逸闭包的属性
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    print("\(#function)")
   guard let httpResponse = navigationResponse.response as? HTTPURLResponse else {
        decisionHandler(.allow)
        return
    }
    
    let policy : WKNavigationResponsePolicy = httpResponse.statusCode == 200 ? .allow : .cancel
    decisionHandler(policy)        
}

// 4) 网页加载成功 
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("\(#function)")
}

// 4) 加载失败
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    print("\(#function)")
   print(error.localizedDescription)
}

WKUIDelegate

主要讲讲网页js的一些函数——api

  • alert()闭包

  • comfirm()

  • prompt()

UIWebView中,js使用上述三个函数,是能够成功弹出的。可是在WKWebView中,使用着三个函数,是不会用任何反应的。缘由是,把这三个函数都分别封装到WKUIDelegate的方法中。但js使用这些函数时,那么客户端会在如下几个协议方法中,监测到发送过来的信息,而后须要用原生代码去实现一个alert。累cry~~~

// MARK: alert
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    let alert = UIAlertController(title: "这是本地代码弹窗", message: message, preferredStyle: .alert)
    lert.addAction(UIAlertAction(title: "ok", style: .cancel, handler: { _ in
            // 必须加入这个 肥调,否则会闪 (逃
        completionHandler()
  }))
    present(alert, animated: true, completion: nil)
}
    
// MARK: comfirm
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
    let alert = UIAlertController(title: "这是本地代码弹窗", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "❤️", style: .default, handler: { _ in
        completionHandler(true)
    }))
    alert.addAction(UIAlertAction(title: "不❤️", style: .default, handler: { _ in
        completionHandler(false)
    }))
    present(alert, animated: true, completion: nil)
}
    
// MARK: prompt
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
    
    let alert = UIAlertController(title: "这是本地代码弹窗", message: prompt, preferredStyle: .alert)
    alert.addTextField { textField in
        textField.placeholder = defaultText
    }
    alert.addAction(UIAlertAction(title: "ok", style: .default, handler: { _ in
        completionHandler(alert.textFields?.last?.text)
    }))
    present(alert, animated: true, completion: nil)
    
}

踩坑

网页适配

有些网页在客户端上显示,会出现一些不适配的状况。使用UIWebView的话,咱们能够用使用其的scaleToFit属性。即webView.scaleToFit = true。可是在WKWebView里,并无这个属性,咱们只能使用到JS注入进行修改。

// 这句至关于给网页注入一个 <meta> 标签,<meta name="viewport" content="width=device-width">
let jsToScaleFit = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"

let scaleToFitScript = WKUserScript(source: jsToScaleFit, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        
let userController = WKUserContentController()
userController.addUserScript(scaleToFitScript)
        
let config = WKWebViewConfiguration()
config.userContentController = userController
        
let wkWeb = WKWebView(frame: view.bounds, configuration: config!)

不过,仍是不太推荐客户端去注入适配代码。最好仍是告知前端,让他们去搞定这问题。我的以为,两端少点干涉仍是比较好滴~~~ (逃

客户端 -> 网页

有时间,咱们须要客户端去调用前端的一些代码。e.g.

// 好比获取网页内容高度
let jsToGetWebHeight = "document.body.offsetHeight"

wkWeb?.evaluateJavaScript(jsToGetWebHeight, completionHandler: { (data, error) in
    print(error?.localizedDescription ?? "执行正确")
    // data 是一个 any 类型,所以须要作好类型判断
    if let webHeight : CGFloat = data as? CGFloat {
        print(webHeight)
    }
})

网页 -> 客户端

不像UIWebViewWKWebView没法使用JaveSciptCore。咱们须要使用到WKScriptMessageHandler这个协议去进行一系列交互。

首先,在初始化阶段,须要使用到WKWebViewConfiguration。放个文档注释先。

/*! @abstract Adds a script message handler.
@param scriptMessageHandler The message handler to add.
@param name The name of the message handler.
@discussion Adding a scriptMessageHandler adds a function window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all frames. */

open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)

name是客户端自定义好的一个字符串类型的命名空间。能够有多个name。网页的js代码,须要在进行交互的地方,使用上

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

来发送消息。

上个栗子?。

let conifg = WKWebViewConfiguration()
config.userContentController.add(self, name: "Test")
let webView = WKWebView(frame: view.bounds, configuration: config)

这是客户端使用WKWebViewConfiguration去初始化一个WKWebView。而且使用到一个nameTestmessageHandler。而在网页须要进行交互的位置,则是加在一句代码。?

<script type="text/javascript">
    function test() {
        
        var message = {
            action: "test",
            params: null,  
            callback: "callback()"
        };
        window.webkit.messageHandlers.Test.postMessage(message);
    }
</script>

对应messageBody载体的格式,貌似没有多大的规定。能够为NSNumber, NSString, NSDate, NSArray, NSDictionary,甚至是NSNull。也就是对应js来讲,应该是NumberStringDatejsonnull

那么问题来了。客户端怎么去作处理呢?

客户端须要去实现WKScriptMessageHandler的协议方法。

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    // 建议是作好判断,毕竟有可能 碰到多种 name 的状况
    if message.name == "Test" {
        // 此处能够去撸 须要 的一些交互了
        print(message.body)
    }
}

跳转app store

WKWebview是没法直接跳转app store。不明白爸爸为何要这样。反正他高兴就好。。。。
那么若是PM硬要跳转app store的话,有两种方式——

1) 砍死PM。。(ps: 我的极度推荐方式一)

2)那么你只能苦逼码码去解决了。

WKNavigationDelegate中,当接受到网页信息的时候,也就是——

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let request = navigationAction.request
       
    if let url = request.url {
        if url.host == "itunes.apple.com" {
           UIApplication.shared.openURL(url)
           decisionHandler(.cancel)
       }
    }
        
    decisionHandler(.allow)
}

有一些古怪的需求

某天,PM跑过来跟你港,想要点击一个网页超连接,而后客户端去push controller,而不是在原页面上刷新。。。

使用UIWebView的时候,其实挺方便的,只须要在UIWebViewDelegate的一个方法中去监听作判断就好。呐。看?。

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    if navigationType == .linkClicked {
        guard let url = request.url?.absoluteString else {
            return true
        }
        // 此处 push 一个 新的 controller 吧
        
        return false
    }
    
    return true
}

可是,WKWebView呢?

须要在WKNavigationDelegate协议方法中,当接收到网页信息的时候——

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let request = navigationAction.request
    
    if let url = request.url {
    // 二、检测打开 <a href> 标签 。若是要打开一个 新web,那么须要 <a href="xx" target="_blank" >,若无 target="_blank",则只会在原web基础上 reload
         if navigationAction.targetFrame == nil {
            // 这里作 push 新的 webview 操做
                       
        }  
    }
    decisionHandler(.allow)
}

结束语

暂时撸到这里吧。估计还有一些坑。后续继续踩,继续更新吧。

最后,上个demo吧。

p2

相关文章
相关标签/搜索