WKWebView

作者: 古月思吉 | 来源:发表于2018-04-18 18:14 被阅读0次
    • 本文讲述的是通过 WKWebView 实现H5页面加载的vc基类BaseWebViewController.swift,主要实现功能:
      (1)加载普通H5页面(不安全链接也能加载)
      (2)获取H5页面的title
      (3)进度条功能实现
      (4)处理JS提示框、确认框、文本输入框
      (5)H5页面加载失败时,显示NoNetworkView(网络出错提示图),并实现重新加载功能
      (6)url重定向
      (7)JS与OC的交互
    1. BaseWebViewController:
    class BaseWebViewController: BaseViewController {
        
        //MARK: - 属性
        public lazy var webView: WKWebView = {
            let configuration = WKWebViewConfiguration.init()
            let webView = WKWebView.init(frame: CGRect.init(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height), configuration: configuration)
            webView.navigationDelegate = self
            webView.uiDelegate = self
            return webView
        }()
        
        private lazy var progressView: UIProgressView = {//进度条
            let progressView = UIProgressView.init(frame: CGRect.init(x: 0, y: 0, width: view.bounds.width, height: 3.0))
            progressView.progress = 0.0
            progressView.tintColor = UIColor.red
            return progressView
        }()
    
        private var urlString: String?//url地址字符串
        private var isProgressBarHidden: Bool = false//是否隐藏ProgressView
        private var hasLoadedSuccess: Bool = false//是否已经加载成功过
        
        private let JSHandlerName_share = "Share"//JS调iOS-分享
        
        //MARK: - 生命周期
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.prepareUI()
        }
        
        deinit {
            if !isProgressBarHidden {
                self.webView.removeObserver(self, forKeyPath: "estimatedProgress")
            }
            self.webView.navigationDelegate = nil
            self.webView.navigationDelegate = nil
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        
        //MARK: - 其他
        
        /// webViewController的初始化配置
        ///
        /// - Parameters:
        ///   - urlString: 地址字符串
        ///   - isNavigationBarHidden: 是否隐藏navigationBar
        ///   - isProgressBarHidden: 是否隐藏progressBar
        public func config(urlString:String, isNavigationBarHidden:Bool = false, isProgressBarHidden:Bool = false) {
            self.urlString = urlString
            self.isNavigationBarHidden = isNavigationBarHidden
            self.isProgressBarHidden = isProgressBarHidden
        }
    
        /// UI初始化
        private func prepareUI() {
            if !isProgressBarHidden {
                self.webView.addSubview(self.progressView)//添加进度条
                self.webView.addObserver(self, forKeyPath: "estimatedProgress", options: NSKeyValueObservingOptions.new, context: nil)//进度监听
            }
            self.view.addSubview(self.webView)
            self.addJSHandlerNames()
            self.loadH5()
            
            if !isNavigationBarHidden {
                //返回按钮点击事件自定义
                self.customGoBack { [weak self] in
                    if (self?.webView.canGoBack)! {
                        self?.webView.goBack()
                    } else {
                        self?.navigationController?.popViewController(animated: true)
                    }
                }
            }
        }
        
        /// 加载H5
        private func loadH5() {
            guard self.urlString != nil else {
                return
            }
            let url = NSURL.init(string: self.urlString!)
            guard url != nil else {
                return
            }
            let request = NSURLRequest.init(url: url! as URL)
            self.webView.load(request as URLRequest)
        }
        
        /**获取监听的代理方法*/
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            if keyPath == "estimatedProgress" && !isProgressBarHidden {
                self.progressView.alpha = 1.0
                self.progressView.setProgress(Float(webView.estimatedProgress), animated: true)
                if(self.webView.estimatedProgress >= 1.0) {//进度条的值最大为1.0
                    UIView.animate(withDuration: 0.3, delay: 0.1, options: UIViewAnimationOptions.curveEaseInOut, animations: { () -> Void in
                        self.progressView.alpha = 0.0
                    }, completion: { (finished:Bool) -> Void in
                        self.progressView.progress = 0
                    })
                }
            }
        }
        
    }
    

    2.WKNavigationDelegate:

    //MARK: - WKNavigationDelegate
    extension BaseWebViewController: WKNavigationDelegate {
        
        //MARK: - 导航监听
        
        /**在发送请求之前,决定是否跳转*/
        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            //...(实现url重定向)
            let urlStr = navigationAction.request.url?.absoluteString
            print(urlStr!)
            decisionHandler(.allow)
        }
        
        /**身份验证*/
        func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            completionHandler(URLSession.AuthChallengeDisposition.useCredential, nil)//设置为nil,不要证书验证
        }
        
        /**在收到响应后,决定是否跳转*/
        func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
            let urlStr = navigationResponse.response.url?.absoluteString
            print(urlStr!)
            decisionHandler(.allow)
        }
        
        /**接收到服务器跳转请求之后调用*/
        func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
            
        }
        
        /**WKNavigation导航错误*/
        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            AJAlertTool.showCancel(message: error.localizedDescription)
            NetworkDataViewManager.addNoNetworkView(toView: self.view, refreshBlock: { [weak self] in
                if self?.hasLoadedSuccess == true {
                    self?.webView.reload()//如果网络加载成功过,直接reload
                } else {
                    self?.loadH5()//如果网页加载一次都没有成功过,则重新发起请求
                }
            })
        }
        
        /**WKWebView终止*/
        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
            
        }
        
        //MARK: - 页面内监听
        
        /**页面开始加载时调用*/
        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            
        }
        
        /**当内容开始返回时调用*/
        func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
            
        }
        
        /**页面加载完成之后调用*/
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            NetworkDataViewManager.removeNoNetworkView(fromView: self.view)
            if !isNavigationBarHidden {
                self.navigationItem.title = self.webView.title
            }
            self.hasLoadedSuccess = true
        }
        
        /**页面加载失败时调用*/
        func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
            AJAlertTool.showCancel(message: error.localizedDescription)
            NetworkDataViewManager.addNoNetworkView(toView: self.view, refreshBlock: { [weak self] in
                if self?.hasLoadedSuccess == true {
                    self?.webView.reload()//如果网络加载成功过,直接reload
                } else {
                    self?.loadH5()//如果网页加载一次都没有成功过,则重新发起请求
                }
            })
        }
        
    }
    

    3.WKUIDelegate:

    //MARK: - WKUIDelegate
    extension BaseWebViewController: WKUIDelegate {
        
        /**WKWebView创建初始化加载的一些配置*/
        func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
            //为防止系统阻止不安全的连接,需要实现下面的方法
            //如果目标主视图不为空,则允许导航
            if !(navigationAction.targetFrame?.isMainFrame != nil) {
                self.webView.load(navigationAction.request)
            }
            return nil
        }
        
        /**JS提示框(ok)处理*/
        func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
            AJAlertTool.showOK(message: message) {
                completionHandler()
            }
        }
        
        /**JS确认弹窗(cancel & ok)处理*/
        func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
            AJAlertTool.showCancelOK(title: nil, message: message, cancelTitle: "取消", cancelBlock: {
                completionHandler(false)
            }, okTitle: "确定") {
                completionHandler(true)
            }
        }
        
        /**js中的文本输入处理*/
        func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
            AJAlertTool.showTextInput(title: prompt, message: nil, defaultTextFieldMessage: defaultText, textFieldPlaceholder: nil, cancelTitle: "取消", cancelBlock: nil, okTitle: "确定") { (textFieldText) in
                completionHandler(textFieldText)
            }
        }
        
    }
    

    4.WKScriptMessageHandler:

    //MARK: - WKScriptMessageHandler
    extension BaseWebViewController:WKScriptMessageHandler {
        
        /**从web界面中接收到一个脚本时调用*/
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if message.name == JSHandlerName_share {
                //分享相关操作...
            }
            //...
        }
        
    }
    

    5.JS交互相关:

    //MARK: - JS交互相关
    extension BaseWebViewController {
        
        //MARK: - JS 调用 OC
        
        /// 添加处理脚本
        private func addJSHandlerNames() {
            self.webView.configuration.userContentController.add(self, name: JSHandlerName_share)
            //...
        }
        
        
        //MARK: - OC 调用 JS
        
        /// 调用JS处理的事件1
        private func JSAction1() {
            //让JS调用HTML文件中的“show()”函数
            self.webView.evaluateJavaScript("show()", completionHandler: { (response, error) in
                //(处理JS执行“show()”函数之后的操作)
            })
        }
        
        //...
        
    }
    
    • 疑问1:
      JS与OC如何进行交互?JS与OC之间的交互能做哪些事情?
      答:
      (1)如果H5页面的某些功能处理效果不太理想,即可通过JS调OC,例如:上传图片、调取相册、分享、支付等native处理效果更好的功能
      (2)如果我们想要点击某个native按钮,让H5页面发生改变,即可OC调JS,比如异步加载大图功能等
    • 疑问2:
      JS交互与重定向,谁更好用?
      答:
      H5页面在执行location.href()函数后,使得OC端重新执行 decidePolicyFor navigationAction 函数,重新进行页面请求,实现重定向,在 decidePolicyFor navigationAction 函数中可以解析H5传递的url,根据OC与H5约定的规则,做相应操作,这样也能实现分享、调取相册等操作。重定向的缺点就是url参数解析比较复杂,而且只实现了“JS调OC”的功能,JS交互可以实现JS调OC,也能实现OC调JS,用法灵活简便。

    6.如何获取H5网页的title、logo?

    • 可以使用js方法获取网页的title、logo
    /**页面加载完成之后调用*/
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            NetworkDataViewManager.removeNoNetworkView(fromView: self.view)
          
            //获取标题
            self.webView.evaluateJavaScript("document.title", completionHandler: { (response, error) in
                let linkTitle = response as? String
            })
            //获取logo
            self.webView.evaluateJavaScript("document.querySelector('link[rel=\"shortcut icon\"]').href", completionHandler: { (response, error) in
                let linkLogo = response as? String
            })
            
        }
    

    参考文章:
    https://www.jianshu.com/p/60b9681dd8d2
    https://www.jianshu.com/p/ab58df0bd1a1

    相关文章

      网友评论

          本文标题:WKWebView

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