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