WKWebView

  • 本文讲述的是通过 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,100评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,862评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,993评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,309评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,303评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,421评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,830评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,501评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,689评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,506评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,564评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,286评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,826评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,875评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,114评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,705评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,269评论 2 341

推荐阅读更多精彩内容