SwiftUI WKWebView обнаруживает изменение URL-адреса

Я быстро учусь. Я работаю со SwiftUI, который является структурой, мне нужно реализовать WKWebView, и в этом URL-адрес изменяется динамически. Мне нужно поймать эти меняющиеся URL-адреса, но решения, которые я пробовал, не работают.

Например: /questions/3841634/kak-ya-mogu-opredelit-kogda-url-stranitsyi-usilitelya-izmenilsya-s-wkwebview/3841642#3841642 Я пробовал этот блок кода, но он не работает и вызывает некоторые ошибки компилятора:

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 в строке структуры ContentView...

7 ответов

Решение

Я нашел очень хорошее решение своего вопроса. Я выложу это здесь. Может быть, кто-то захочет это увидеть и может быть им полезен.

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

Вы можете просто создать класс модели ObservableObject для веб-просмотра с именем "WebViewModel", например

class WebViewModel: ObservableObject {
    @Published var link: String
    @Published var didFinishLoading: Bool = false

    init (link: String) {
        self.link = link
    }
} 

а также импорт

import WebKit
import Combine

а затем скопируйте эти фрагменты кода

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 
                }
        }
   }}

таким образом вы можете получить ответы других делегатов.

Вы используете это для делегатов 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)
}

Просто, просто используйте этот метод делегата

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

Используйте следующую функцию делегата 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 который показывает лучший способ перехвата каждого network позвонить или Url изменение и получение предстоящих Url связанные с data. Здесь также показано, как реализоватьWebView в SwiftUI, взаимодействуя с JavaScript функции и загрузка локального .html файл из проекта iOS

Вы можете использовать наблюдение за ключом / значением, чтобы обнаружить изменения в свойстве url WKWebView.

Вот простой пример обертывания 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
    }
}

Затем вы можете прослушать уведомление об изменении URL-адреса в onReceive() контейнера, содержащего WebView:

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

Я пришел сюда, пытаясь быстро получить рабочий образец в SwiftUI для получения ответа HTML от службы веб-аутентификации. (в частности, новая ужасная схема аутентификации DropBox с использованием URI... мы не видим этих деталей, но обратные вызовы и код должны быть достаточно пояснительными. (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)
    
}
Другие вопросы по тегам