WKWebView

  • iOS8 以前,我们都是使用 UIWebView 来加载网页.
  • UIWebView 缺点:
    • 1 占用内存大
    • 2 内存泄露
    • 3 代理方法有限,和JS的交互不方便
  • iOS8 中引入了新的 WebView 控件: WKWebView,目的是给出一个新的高性能的 Web View 解决方案,摆脱过去 UIWebView 的老旧笨重特别是内存占用量巨大的问题,它使用 Nitro JavaScript 引擎,这意味着所有第三方浏览器运行JavaScript将会跟 safari 一样快.
  • 苹果将 UIWebViewUIWebViewDelegate 重构成了 143协议,引入了不少新的功能和接口,这可以在一定程度上看做苹果对其封锁 Web View 内核的行为作出的补偿.众所周知,连 ChromeiOS 版用的也是 UIWebView 的内核。
  • WKWebView 有以下几大主要进步:
    • 将浏览器内核渲染进程提取出 App,由系统进行统一管理,这减少了相当一部分的性能损失。
    • JSNative 交互方便
    • 支持高达 60 fps 的滚动刷新率,内置了手势探测
    • 所提供的接口也丰富了

WKWebView 使用

  • 新建一个项目
  • ViewController 导入 WebKit 框架:import WebKit
  • ViewController 懒加载 WKWebView
  • // MARK: - 懒加载
    lazy var wkWebView: WKWebView = WKWebView()
    
  • loadView 方法中,将 wkWebView 设置为控制器的view
  • override func loadView() {
      super.loadView()
    
      view = wkWebView
    }
    
  • 使用 WKWebView 加载 http:image.baidu.com
  • import UIKit
    import WebKit
    //
    class ViewController: UIViewController {
    
      override func loadView() {
          super.loadView()
    
          view = wkWebView
      }
    
      override func viewDidLoad() {
          super.viewDidLoad()
    
          let url = NSURL(string: "http:image.baidu.com")!
          let request = NSURLRequest(URL: url)
          wkWebView.loadRequest(request)
      }
    
      // MARK: - 懒加载
      lazy var wkWebView: WKWebView = WKWebView()
    }
    
  • 效果如下
  • 可以发现 WKWebView 的内存占用明显比 UIWebView 小多了

UIWebView代理

  • UIWebViewDelegate 代理方法如下
  • public protocol UIWebViewDelegate : NSObjectProtocol {
    
      @available(iOS 2.0, *)
      /// webView 是否需要加载某个请求
      optional public func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
    
      @available(iOS 2.0, *)
      /// webView开始加载某个请求
      optional public func webViewDidStartLoad(webView: UIWebView)
    
      @available(iOS 2.0, *)
      /// webView 已经加载某个请求
      optional public func webViewDidFinishLoad(webView: UIWebView)
    
      @available(iOS 2.0, *)
      /// webView 加载请求失败
      optional public func webView(webView: UIWebView, didFailLoadWithError error: NSError?)
    }
    

WKWebView代理 包括 WKNavigationDelegateWKUIDelegate

  • WKNavigationDelegate 代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转等
/*! A class conforming to the WKNavigationDelegate protocol can provide
 methods for tracking progress for main frame navigations and for deciding
 policy for main frame and subframe navigations.
*/

public protocol WKNavigationDelegate : NSObjectProtocol {

    @available(iOS 8.0, *)
    /// 在发送请求之前,决定是否加载
    optional public func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void)

    @available(iOS 8.0, *)
    /// 在收到响应后,决定是否加载
    optional public func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)

    /// 页面开始加载后调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)

    /// 接收到服务器重定向后调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)

    /// 页面加载失败后调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError)

    /// 当内容开始返回时调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didCommitNavigation navigation: WKNavigation!)

    /// 加载完成时调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!)

    /// 页面加载失败后调用
    @available(iOS 8.0, *)
    optional public func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError)


    /// 页面中止加载时调用
    @available(iOS 9.0, *)
    optional public func webViewWebContentProcessDidTerminate(webView: WKWebView)
}
  • WKUIDelegate 代理提供和UI相关的方法
  • /*! A class conforming to the WKUIDelegate protocol provides methods for
    presenting native UI on behalf of a webpage.
    */
    public protocol WKUIDelegate : NSObjectProtocol {
    
      /// 创建一个新的WebView
      @available(iOS 8.0, *)
      optional public func webView(webView: WKWebView, createWebViewWithConfiguration configuration: WKWebViewConfiguration, forNavigationAction navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?
    
      /// 已经关闭时调用
      @available(iOS 9.0, *)
      optional public func webViewDidClose(webView: WKWebView)
    
      /**
       *  Web界面中有弹出警告框时调用
       *
       *  @param webView           实现该代理的Webview
       *  @param message           警告框中的内容
       *  @param frame             主窗口
       *  @param completionHandler 警告框消失调用
       */
      @available(iOS 8.0, *)
      optional public func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void)
    
      /// Web界面中有弹出确认框时调用
      @available(iOS 8.0, *)
      optional public func webView(webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: (Bool) -> Void)
    
      /// Web界面中有弹出警告框时调用
      @available(iOS 8.0, *)
      optional public func webView(webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: (String?) -> Void)
    }
    

WKNavigationDelegate 使用

  • 设置 selfwkWebViewnavigationDelegate 代理
  • override func loadView() {
      super.loadView()
    
      view = wkWebView
    
      wkWebView.navigationDelegate = self
    }
    
  • 实现 webView:decidePolicyForNavigationAction: decisionHandler 是否加载某个请求
  • // MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
    extension ViewController: WKNavigationDelegate {
      func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
          print("request: \(navigationAction.request)")
      }
    }
    
  • 运行会报错: Completion handler passed to -[WKWebView.ViewController webView:decidePolicyForNavigationAction:decisionHandler:] was not called
  • 也就是说在 webView:decidePolicyForNavigationAction: decisionHandler 代理方法里面必须调用 decisionHandler 这个闭包
  • decisionHandler方法中传入 Allow -> 同意加载, Cancel -> 取消加载
  • // MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
    extension ViewController: WKNavigationDelegate {
      func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
          print("request: \(navigationAction.request)")
    
          // Allow:同意加载, Cancel:取消加载
          decisionHandler(.Allow)
      }
    }
    
  • WKWebView 监听 开始加载加载完成加载失败
  • // MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
    extension ViewController: WKNavigationDelegate {
      /// 页面开始加载后调用
      func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
          print("页面开始加载: \(webView.URL?.absoluteString)")
      }
    
      /// 加载完成时调用
      func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
          print("加载完成时调用: \(webView.URL?.absoluteString)")
      }
    
      /// 页面加载失败后调用
      func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError) {
          print("页面加载失败后调用: \(webView.URL?.absoluteString)")
      }
    
      /// 在发送请求之前,决定是否加载
      func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
          print("request: \(navigationAction.request)")
    
          // Allow:同意加载, Cancel:取消加载
          decisionHandler(.Allow)
      }
    }
    

WKWebView 实现 前进后退刷新进度条

  • 修改 wkWebView 的界面,添加 toolBar 来实现 前进后退刷新
  • // MARK: - 懒加载
    /// WKWebView
    lazy var wkWebView: WKWebView = WKWebView()
    
    /// UIToolbar
    private lazy var toolBar: UIToolbar = UIToolbar()
    
    /// 后退item
    private lazy var backItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Rewind, target: self, action: "back")
    
    /// 前进item
    private lazy var forwardItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FastForward, target: self, action: "forward")
    
    /// 刷新item
    private lazy var refreshItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Refresh, target: self, action: "refresh")
    
  • 定义 prepareUI 方法,添加 wkWebViewtoolBar 到控制器的 view
  • private func prepareUI() {
      self.view.addSubview(wkWebView)
      self.view.addSubview(toolBar)
      setupToolBar()
    
      wkWebView.translatesAutoresizingMaskIntoConstraints = false
      toolBar.translatesAutoresizingMaskIntoConstraints = false
      progress.translatesAutoresizingMaskIntoConstraints = false
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[webView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView]))
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[toolBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["toolBar": toolBar]))
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[webView]-0-[toolBar(44)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView, "toolBar": toolBar]))
    }
    
  • viewDidLoad 里面调用 prepareUI 方法
  • 添加 前进后退刷新 这3个 UIBarButtonItemtoolBar 里面
  • /// 设置toolBar
    private func setupToolBar() {
      backItem.enabled = false
      forwardItem.enabled = false
    
      let flexibleF = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
    
      let flexibleS = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
    
      let items = [backItem,flexibleF, forwardItem, flexibleS, refreshItem]
      toolBar.items = items
    }
    
    // MARK: - 按钮点击事件
    /// 回退
    func back() {
      wkWebView.goBack()
    }
    
    /// 前进
    func forward() {
      wkWebView.goForward()
    }
    
    /// 刷新
    func refresh() {
      let request = NSURLRequest(URL: wkWebView.URL!)
      wkWebView.loadRequest(request)
    }
    
  • 使用 KVO 来监听 WKWebView 加载状态,判断是否可以 前进后退
  • 监听 WKWebViewloading 属性
  • override func viewDidLoad() {
      super.viewDidLoad()
    
      prepareUI()
    
      // 允许手势左划回退到上一个界面
      wkWebView.allowsBackForwardNavigationGestures = true
    
      wkWebView.navigationDelegate = self
    
      // 监听 webView加载状态
      wkWebView.addObserver(self, forKeyPath: "loading", options: NSKeyValueObservingOptions.New, context: nil)
    
      let url = NSURL(string: "http:image.baidu.com")!
      let request = NSURLRequest(URL: url)
      wkWebView.loadRequest(request)
    }
    
  • 实现监听
  • override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
      if keyPath == "loading" {
          backItem.enabled = wkWebView.canGoBack
          forwardItem.enabled = wkWebView.canGoForward
      }
    }
    
  • 效果如下:
  • 到此 WKWebView前进后退刷新 功能就实现了
  • 实现 WKWebView 加载进度条
  • 懒加载 进度条
  • /// 进度条
      private lazy var progress: UIProgressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Bar)
    
  • prepareUI 添加 进度条 为控制器 view 的子控件,并添加约束,让进度条在 toolBar上面
  • private func prepareUI() {
      self.view.addSubview(wkWebView)
      self.view.addSubview(toolBar)
      self.view.addSubview(progress)
      progress.tintColor = UIColor.orangeColor()
      setupToolBar()
    
      wkWebView.translatesAutoresizingMaskIntoConstraints = false
      toolBar.translatesAutoresizingMaskIntoConstraints = false
      progress.translatesAutoresizingMaskIntoConstraints = false
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[webView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView]))
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[toolBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["toolBar": toolBar]))
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[progress]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["progress": progress]))
    
      view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[webView]-0-[progress(2)]-0-[toolBar(44)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView, "toolBar": toolBar, "progress": progress]))
    }
    
  • viewDidLoad 监听 WKWebView 加载进度 estimatedProgress 属性的改变
  • wkWebView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)
    
  • observeValueForKeyPath 里面设置进度条的进度
  • /// KVO监听方法
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
      // 加载状态
      if keyPath == "loading" {
          backItem.enabled = wkWebView.canGoBack
          forwardItem.enabled = wkWebView.canGoForward
      }
    
      // 加载进度
      if (keyPath == "estimatedProgress") {
          progress.hidden = wkWebView.estimatedProgress == 1
          progress.setProgress(Float(wkWebView.estimatedProgress), animated: false)
      }
    }
    
  • 效果如下:

WKWebView 弹框处理

  • toolBar 添加 alertItem 来执行 JSalert 函数, 通过使用 WKWebViewevaluateJavaScript 来调用 JS 代码
  • /// 执行js alert
    func runAlert() {
      wkWebView.evaluateJavaScript("alert('js弹出来的警告框')") { (obj, error) -> Void in
          print("obj: \(obj), error: \(error)")
      }
    }
    
  • WKWebView 中调用 js 的 alert 不会弹出提示框.需要设置 WKWebViewUIDelegate, 在 UIDelegatewebView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler 自己来弹出警告框
  • 设置 wkWebView.UIDelegate = self, 让 ViewController 实现 WKUIDelegate 协议
  • // MARK: - 扩展 ViewController 实现 WKUIDelegate 协议
    extension ViewController: WKUIDelegate {
      func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
    
      }
    }
    
  • 当实现 UIDelegatewebView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler 而没有调用 completionHandler 时系统会报错:Completion handler passed to -[WKWebView.ViewController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:] was not called
  • // MARK: - 扩展 ViewController 实现 WKUIDelegate 协议
    extension ViewController: WKUIDelegate {
        /// Web界面中有弹出警告框时调用
        func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
            // 必须调用,否则报错
            completionHandler()
    
            // WKWebView不会自己调用系统的AlertView,需要我们自己来调用AlertController弹框
            let alert = UIAlertController(title: "js 调用alert, iOS拦截到,弹出UIAlertController", message: "\(message)", preferredStyle: .Alert)
            alert.addAction(UIAlertAction(title: "ok", style: .Default, handler:nil))
            alert.addAction(UIAlertAction(title: "cancel", style: .Cancel, handler: nil))
            self.presentViewController(alert, animated: true, completion: nil)
        }
    }
    

JS 调用 iOS 本地(Native)代码

  • ViewController 里面懒加载 2个 UIBarButtonItem, 当点击的时候让 JS 调用本地代码
  • /// JSCallNative item
    private lazy var JSPrintMessageItem: UIBarButtonItem = UIBarButtonItem(title: "JSPrintMessage", style: UIBarButtonItemStyle.Plain, target: self, action: "JSPrintMessage")
    
    /// JSCallNative item
    private lazy var JSModalVCItem: UIBarButtonItem = UIBarButtonItem(title: "JSModalVC", style: UIBarButtonItemStyle.Plain, target: self, action: "JSModalVC")
    
  • 将这2个 UIBarButtonItem 添加到 toolBar 上,效果如下:
  • WKWebView 里面处理 JS 调用 本地代码 的方式和 UIWebView 不一样. UIWebView 是通过 拦截请求 的方式. WKWebView 是在 WKWebView 初始化时 addScriptMessageHandler(self, name: "callNative"), JS 中执行 window.webkit.messageHandlers.callNative(用户自定义).postMessage(message); message一般为字典,可以传入多个值, WKWebViewWKScriptMessageHandler 代理的 userContentController:didReceiveScriptMessage: 方法会被调用.在这个方法里面处理 JS 传来的值
  • 修改 WKWebView 懒加载代码,在懒加载添加 WKWebViewConfiguration 并设置 addScriptMessageHandler
  • // MARK: - 懒加载
    /// WKWebView
    private lazy var wkWebView: WKWebView = {
      let config = WKWebViewConfiguration()
      /// 设置 JS中调用本地代码的方法为: callNative,WKWebView会回调 WKScriptMessageHandler 协议的 userContentController: WKUserContentController:didReceiveScriptMessage: 方法
      config.userContentController.addScriptMessageHandler(self, name: "callNative")
    
      let webView = WKWebView(frame: CGRectZero, configuration: config)
    
      return webView
    }()
    
  • 由于我们现在加载的是 http:image.baidu.com, 这个 HTML 里面没有我们自己写好的 JS 代码.可以在页面加载完成的时候来注入 JS 代码
  • 准备 JS 代码,放在 index.js, 并添加到工程
  • function JSPrintMessage() {
      var message = {
          'method' : 'printMessage',
          'param1' : 'js call swift',
      };
      window.webkit.messageHandlers.callNative.postMessage(message);
    }
    // 
    function JSModalVC() {
      var message = {
          'method' : 'modalVC',
          'param1' : 'TestViewController',
      };
      window.webkit.messageHandlers.callNative.postMessage(message);
    }
    
  • index.js 文件里面的 JS 代码加载到 ViewController 里面来
  • /// js代码
    private lazy var JSCode: String = {
      let url = NSBundle.mainBundle().URLForResource("index.js", withExtension: nil)
      return try! String(contentsOfURL: url!, encoding: NSUTF8StringEncoding)
    }()
    
  • WKWebView 加载完成回调的 WKNavigationDelegatewebView:didFinishNavigation: 方法中来注入我们刚才写好的 JS 代码
  • /// 网页加载完成时调用
    func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
      print("加载完成时调用: \(webView.URL?.absoluteString)")
    
      // 注入js代码
      webView.evaluateJavaScript(JSCode) { (result, error) -> Void in
          // 检查 JS 代码中是否有 JSPrintMessage 这个函数,若打印 function 表示注入成功
          let jsCheck = "typeof JSPrintMessage;"
    
          webView.evaluateJavaScript(jsCheck, completionHandler: { (result, error) -> Void in
              if error == nil && (result as? String) == "function" {
                  print("注入js代码成功")
              } else {
                  print("注入js代码失败")
              }
          })
      }
    }
    
  • 扩展 ViewController 实现 WKScriptMessageHandler 协议
  • // MARK: - 扩展 ViewController 实现 WKScriptMessageHandler 协议
    extension ViewController: WKScriptMessageHandler {
      /// 当 JS 中调用 window.webkit.messageHandlers.callNative.postMessage(message); 方法就会执行这个方法
      /// 其中callNative 为自定义,需要在WKWebView注册
      func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
          // 获取JS传过来的参数
          let dict = message.body as! [String: String]
    
          // 获取JS要调用的方法名称
          let method = dict["method"]
    
          // 根据不同方法
          if method == "printMessage" {
              let message = dict["param1"]!
              printMessage(message)
          } else if (method == "modalVC") {
              let message = dict["param1"]!
              modalVC(message)
          }
      }
    
      /// JS 调用本地方法,控制台输出
      func printMessage(message: String) {
          print("printMessage: \(message)")
      }
    
      /// JS 调用本地方法,Modal出新的控制器
      func modalVC(vcName: String) {
          // Swift的类名需要使用: ProductName.ClassName,本项目ProductName为WKWebView
          let cls = NSClassFromString("WKWebView." + vcName) as? UIViewController.Type
          let vc = cls?.init()
          self.presentViewController(UINavigationController(rootViewController: vc!), animated: true, completion: nil)
      }
    }
    
  • 当点击 toolBar 上面的 JSPrintMessage Item 时调用以下代码
  • /// Swift 执行 JS 的 JSPrintMessage() 方法, JS调用Native(Swift/OC)
    func JSPrintMessage() {
      wkWebView.evaluateJavaScript("JSPrintMessage()") { (result, error) -> Void in
      }
    }
    
  • 在控制台可以打印JS传过来的值:printMessage: js call swift
  • 当点击 toolBar 上面的 JSModalVC Item 时调用以下代码
  • /// Swift 执行 JS 的 JSModalVC() 方法,JS调用Native(Swift/OC)
    func JSModalVC() {
      wkWebView.evaluateJavaScript("JSModalVC()") { (result, error) -> Void in
      }
    }
    
  • 最终会调用 modalVC 方法,Modal出新的控制器
  • /// JS 调用本地方法,Modal出新的控制器
    func modalVC(vcName: String) {
      // Swift的类名需要使用: ProductName.ClassName,本项目ProductName为WKWebView
      let cls = NSClassFromString("WKWebView." + vcName) as? UIViewController.Type
      let vc = cls?.init()
      self.presentViewController(UINavigationController(rootViewController: vc!), animated: true, completion: nil)
    }
    

results matching ""

    No results matching ""