WKWebView是 在iOS 8后推出要替代UIWebView。相对于成熟的UIWebView来讲,这个后生仔在使用上还是有点点小坑的~
在初始化上,WKWebView 和 UIWebView 没有多大的差异。
// WKWebView let wkWeb = WKWebView(frame: view.bounds) // 一些代理 wkWeb.navigationDelegate = self wkWeb.uiDelegate = self // UIWebView let web = UIWebView(frame: view.bounds) // 一些代理 web.delegate = self二者在初始化上还是蛮像的。一个图样的我。(逃
仔细翻开了WKWebView,发现其还提供一个初始化方法。
public init(frame: CGRect, configuration: WKWebViewConfiguration)
也就是可以用WKWebViewConfiguration 去init一个WKWebView。
后面再继续港 WKWebViewConfiguration。
WKNavigationDelegate的协议方法还是挺多的。
// 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) }主要讲讲网页js的一些函数——
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) } })不像UIWebView,WKWebView无法使用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。并且使用到一个name为Test的messageHandler。而在网页需要进行交互的位置,则是加在一句代码。��
<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来说,应该是Number,String,Date,json,null。
那么问题来了。客户端怎么去做处理呢?
客户端需要去实现WKScriptMessageHandler的协议方法。
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // 建议是做好判断,毕竟有可能 碰到多种 name 的情况 if message.name == "Test" { // 此处可以去撸 需要 的一些交互了 print(message.body) } }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 { // 2、检测打开 <a href> 标签 。如果要打开一个 新web,那么需要 <a href="xx" target="_blank" >,若无 target="_blank",则只会在原web基础上 reload if navigationAction.targetFrame == nil { // 这里做 push 新的 webview 操作 } } decisionHandler(.allow) }暂时撸到这里吧。估计还有一些坑。后续继续踩,继续更新吧。
最后,上个demo吧。
7月29日发布