【问题标题】:SwiftUI WKWebView detect url changingSwiftUI WKWebView 检测 url 变化
【发布时间】:2020-02-08 16:45:39
【问题描述】:

我学得很快。我使用 SwiftUI 这是一个结构,我必须实现一个 WKWebView ,其中一个 url 是动态变化的。我必须捕捉这些不断变化的网址,但我尝试过的解决方案不起作用。

例如:https://stackoverflow.com/a/48273950/10088243 我试过这个代码块,但它不工作,它给了我一些编译器错误:

import SwiftUI
import WebKit

struct ContentView: UIViewRepresentable, WKNavigationDelegate {

    let request = URLRequest(url: URL(string: "https://apple.com")!)

    func makeUIView(context: Context) -> WKWebView  {
    let preferences = WKPreferences()
    preferences.javaScriptEnabled = true
    preferences.javaScriptCanOpenWindowsAutomatically = true

    let configuration = WKWebViewConfiguration()
    configuration.preferences = preferences
    let webView = WKWebView(frame: .zero, configuration: configuration)
    webView.allowsBackForwardNavigationGestures = true


    return webView
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
 // 'override' can only be specified on class membe
  if keyPath == #keyPath(WKWebView.url) {
    print("### URL:", self.webView.url!)
  }

  if keyPath == #keyPath(WKWebView.estimatedProgress) {
    // When page load finishes. Should work on each page reload.
    if (self.webView.estimatedProgress == 1) {
      print("### EP:", self.webView.estimatedProgress)
    }
  }
}

func updateUIView(_ uiView: WKWebView, context: Context) {
    uiView.load(request)
}

func webViewDidFinishLoad(webView : WKWebView) {
    print("Loaded: \(String(describing: webView.url))")
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("Loaded: \(String(describing: webView.url))")
    //progressView.isHidden = true
}

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    //progressView.isHidden = false
    print("Loaded: \(String(describing: webView.url))")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我有一个非类类型“ContentView”不能符合类协议“NSObjectProtocol”错误,在行 struct ContentView...

【问题讨论】:

    标签: ios swift wkwebview swift5


    【解决方案1】:

    您可以简单地创建一个名为“WebViewModel”的 webview 的 ObservableObject 模型类

    class WebViewModel: ObservableObject {
        @Published var link: String
        @Published var didFinishLoading: Bool = false
    
        init (link: String) {
            self.link = link
        }
    } 
    

    还有导入

    import WebKit
    import Combine
    

    然后复制这段代码sn-ps

    struct SwiftUIWebView: UIViewRepresentable {
        @ObservedObject var viewModel: WebViewModel
    
        let webView = WKWebView()
    
        func makeUIView(context: UIViewRepresentableContext<SwiftUIWebView>) -> WKWebView {
            self.webView.navigationDelegate = context.coordinator
            if let url = URL(string: viewModel.link) {
                self.webView.load(URLRequest(url: url))
            }
            return self.webView
        }
    
        func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<SwiftUIWebView>) {
            return
        }
    
        class Coordinator: NSObject, WKNavigationDelegate {
            private var viewModel: WebViewModel
    
            init(_ viewModel: WebViewModel) {
                self.viewModel = viewModel
            }
    
            func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                //print("WebView: navigation finished")
                self.viewModel.didFinishLoading = true
            }
        }
    
        func makeCoordinator() -> SwiftUIWebView.Coordinator {
            Coordinator(viewModel)
        }
    }
    
    
    
    struct SwiftUIWebView_Previews: PreviewProvider {
        static var previews: some View {
            
            SwiftUIWebView(viewModel: WebViewModel(link: "https://google.com"))
            //WebView(request: URLRequest(url: URL(string: "https://www.apple.com")!))
        }
    }
    

    在你看来

    struct AnyView: View {
        @ObservedObject var model = WebViewModel(link: "https://www.wikipedia.org/")
    
        
    var body: some View {
            
            
        NavigationView {
           SwiftUIWebView(viewModel: model)
                    if model.didFinishLoading {
                        //do your stuff 
                    }
            }
       }}
    

    这样您就可以得到其他代表的响应。

    【讨论】:

    • 谢谢,这是一个很好的答案,但现在我不会在生产中使用 SwiftUI。我回到故事板。
    • @Akhtar 你能修复 AnyView 中的缩进吗?
    • 为我工作:)
    【解决方案2】:

    您使用它来代表 WKNavigationProtocol 执行(例如,允许或取消 URL 加载)您的操作

        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let host = navigationAction.request.url?.host {
            if host.contains("facebook.com") {
                decisionHandler(.cancel)
                return
            }
        }
    
        decisionHandler(.allow)
    }
    

    【讨论】:

    • 如何在 SwiftUI 中实现这些?我需要代码示例。我是 Swift 世界的新手
    • 查看答案,我已经用精确的委托方法示例更新了它@AlparslanSelçukDevelioğlu
    • 问题是:在 SWIFTUI 中不能这样写。因为你不能这样写: struct SwiftUIView: UIViewRepresentable, WKNavigationDelegate 在struct中,你不能继承2个类。您必须内联编写此函数。整个互联网都充满了您的答案,但感谢您的努力。我找到了解决方案。
    【解决方案3】:

    使用WKWebView的如下委托函数:

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
      // Suppose you don't want your user to go a restricted site
      if let host = navigationAction.request.url?.host {
          if host == "restricted.com" {
              decisionHandler(.cancel)
              return
          }
      }
      decisionHandler(.allow)
    }
    

    您可以从Medium 阅读this 文章,该文章显示了拦截每个network 呼叫或Url 更改和获取即将到来的Url 相关data 的更好方法。它还展示了如何在SwiftUI 中实现WebView,与JavaScript 函数接口并从iOS 项目加载本地.html 文件

    【讨论】:

      【解决方案4】:

      您可以使用键/值观察来检测 WKWebView 的 url 属性的变化。

      这是一个将 WKWebView 包装在 UIViewRepresentable 中的简单示例。

      请注意,因为我们正在修改属性,所以 UIViewRepresentable 是 final class 而不是结构。

      import Combine
      import SwiftUI
      import WebKit
      
      final class WebView: UIViewRepresentable {
      
          @Published var url: URL? = nil {
              didSet {
                  if url != nil {
                      willChange.send(url)
                  }
              }
          }
      
          private let view = WKWebView()
      
          private var urlChangedObservation: NSKeyValueObservation?
          private let willChange = PassthroughSubject<URL?, Never>()
      
          func makeUIView(context: Context) -> WKWebView {
              return makeWebView()
          }
      
          func updateUIView(_ uiView: WKWebView, context: Context) {
          }
      
          func display(_ html: String) {
              self.view.loadHTMLString(html, baseURL: nil)
          }
      
          public func load(_ url: String) -> WebView {
              let link = URL(string: url)!
              let request = URLRequest(url: link)
              self.view.load(request)
              return self
          }
      
          func makeWebView() -> WKWebView {
              self.urlChangedObservation = self.view.observe(\WKWebView.url, options: .new) { view, change in
                  if let url = view.url {
                      self.url = url
                  }
              }
              return self.view
          }
      }
      

      然后你可以在持有 WebView 的容器的 onReceive() 中监听 url 修改通知:

      .onReceive(self.webview.$url) { url in
                          if let url = url {
                      }
      }
      

      【讨论】:

        【解决方案5】:

        我来到这里尝试一种快速的方法来获取 SwiftUI 中的工作示例,以从 Web 身份验证服务获取 HTML 响应。 (具体来说,使用 URI 的新 DropBox awful 身份验证模式......我们没有看到这个细节,但回调和代码应该足够解释。(JSOn 来自我在URI))

        在我们的 Swift UI 部分:

        struct ContentView: View {
            @State private var showingSheet = false
        
            private var webCallBack: WebCallBack = nil
        
            let webView = WKWebView(frame: .zero)
            @State private var auth_code = ""
            
            var body: some View {
                VStack{
                    Text("\(auth_code)")
                       .font(.system(size: 50))
                    Button("Show Auth web form") {
                                self.showingSheet = true
                            }
                            .sheet(isPresented: $showingSheet) {
                                WebView( webView: webView, webCallBack: { (d: Dict?) in
                                    print("\n", d)
                                    auth_code = (d?["auth_code"] as? String) ?? "!!"
                                    showingSheet = false
                                } )
                            }
                }
            }
        }
        

        我们的实现:

        typealias WebCallBack = ( (Dict?)->() )?
        
        class MyWKDelegate: NSObject, WKNavigationDelegate{
                
            private var webCallBack : WebCallBack = nil
            
            init(webCallBack: WebCallBack) {
                self.webCallBack = webCallBack
            }
            
            func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                print("End loading")
                
                webView.evaluateJavaScript("document.body.innerHTML", completionHandler: { result, error in
                    
                    if let html = result as? String {
                        //print(html)
                        // we are here also at first call, i.e. web view with user / password. Custiomize as needed.
                        if let d = dictFromJSONWith(string: html){
                            //print(d)
                            self.webCallBack?(d)
                        }
                    }
                })
            }
        }
        
            
        struct WebView: UIViewRepresentable {
        
            let webView: WKWebView
            let delegate: MyWKDelegate
        
            internal init(webView: WKWebView, webCallBack: WebCallBack) {
                self.webView = webView
                self.delegate = MyWKDelegate(webCallBack: webCallBack)
        
                webView.navigationDelegate = delegate
                
                let urlStr = DB_URL.replacingOccurrences(of: "APP_KEY", with: APP_KEY).replacingOccurrences(of: "REDIRECT_URI", with: REDIRECT_URI)
                print(urlStr)
        
                if let url = URL(string: urlStr){
                webView.load(URLRequest(url: url))
                }
        
            }
        
        
            func makeUIView(context: Context) -> WKWebView {
                return webView
            }
            
            func updateUIView(_ uiView: WKWebView, context: Context) { }
        }
        

        一些让生活更轻松的辅助代码:

        typealias Dict = [String : Any]
        typealias Dicts = [Dict]
        
        
        func dictFromJSONWith(data: Data?)->Dict? {
            
            guard let data = data else {
                return nil
            }
            if let dict = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions() ){
                return dict as? Dict
            }
            return nil
        }
        
        
        func dictFromJSONWith(string: String?)->Dict?{
            
            guard let data = string?.data(using: .utf8) else{
                return nil
            }
            return dictFromJSONWith(data: data)
            
        }
        

        【讨论】:

          【解决方案6】:

          简单,就用这个委托方法

          func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                  print(webView.url?.absoluteString)
              }
          

          【讨论】:

          • 这如何解决 OP 的问题? (与其他答案不同?)
          • wkwebview 有一个委托方法“didFinish”,它会在成功加载 url 时触发。同样在成功加载 url 时,wkwebview url 也会更新。所以一个非常简单的方法是通过 web view.url 在 didFinish 委托方法上加​​载后获取新的 url。我希望我已经回答了你的问题! :)
          【解决方案7】:

          我为我的问题找到了一个很好的解决方案。我会把它贴在这里。也许有人想看到它并且可能对他们有用。

          observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
              if let url = view.url {
                  // do something with your url
              }
          }
          

          【讨论】:

          • 对不起,这个答案没有描述如何实现。 “观察。观察”?你从哪里得到的?你把它放在哪里?实际完整的工作代码怎么样?
          • 你是对的。但不幸的是,我现在无法访问该项目和代码。工作改变了
          猜你喜欢
          • 2022-06-18
          • 1970-01-01
          • 2021-10-15
          • 2016-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多