SwiftUI中如何从类取值更新浏览器地址栏TextField
如何监听WebView URL变化并同步更新地址栏TextField
我正在开发一款Web浏览器应用,想要通过Coordinator类的init方法,利用urlObservation获取当前加载网页的URL,并用该字符串更新地址栏的TextField,但尝试修改后没有效果。
原有代码
WebView 代码
struct WebView: NSViewRepresentable { // ... 其他代码 class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate, ObservableObject { private var viewModel: WebStateModel private var wkWebview = WKWebView() var urlObservation: NSKeyValueObservation? init(_ viewModel: WebStateModel, _ wkWebview: WKWebView) { self.viewModel = viewModel self.wkWebview = WKWebView() // 这里重新创建了WebView实例 self.urlObservation = wkWebview.observe(\.url, changeHandler: { (wkWebview, change) in print((wkWebview.url?.absoluteString ?? "Empty")) }) } // ... 其他代码 } }
ContentView 代码
@State var webView: WebView? @State var text = "" // ... 其他代码 // 地址栏 TextField("输入URL", text: Binding( get: { text }, set: { text = WebStateModel.stripHttps($0) } ), onCommit: { webModel.updateUrl(text) })
尝试的修改(无效)
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate, ObservableObject { private var viewModel: WebStateModel private var wkWebview = WKWebView() var urlObservation: NSKeyValueObservation? @Published var text = "" init(_ viewModel: WebStateModel, _ wkWebview: WKWebView) { self.viewModel = viewModel self.wkWebview = WKWebView() // 依然重新创建WebView super.init() self.urlObservation = wkWebview.observe(\.url, changeHandler: { (wkWebview, change) in print((wkWebview.url?.absoluteString ?? "Empty")) self.text = ((wkWebview.url?.absoluteString ?? "Empty")) }) } }
问题根源
- WebView实例不匹配:Coordinator的init方法里重新创建了
WKWebView,导致你监听的是一个从未被添加到界面的空实例,实际加载页面的WebView并没有被监听。 - 状态未绑定:Coordinator的
@Published var text没有和ContentView的@State var text建立关联,即使Coordinator的text更新,ContentView也无法感知。 - KVO使用不够可靠:直接监听
url属性可能在URL变化过程中触发多次,不如用WKNavigationDelegate的回调更稳定。
解决方法
步骤1:修复WebView实例问题
Coordinator中不要重新创建WKWebView,直接使用传入的实例:
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate { private let viewModel: WebStateModel private var urlObservation: NSKeyValueObservation? private weak var wkWebView: WKWebView? init(_ viewModel: WebStateModel) { self.viewModel = viewModel super.init() } // 单独设置KVO监听,传入实际使用的WebView func setupUrlObservation(for webView: WKWebView) { self.wkWebView = webView urlObservation = webView.observe(\.url, options: [.new]) { [weak self] webView, _ in guard let self = self, let urlStr = webView.url?.absoluteString else { return } // 同步到ViewModel,让ContentView可以绑定 self.viewModel.currentUrl = urlStr } } }
步骤2:优化ViewModel作为数据中转站
让WebStateModel持有当前URL的状态,通过@Published暴露给视图:
class WebStateModel: ObservableObject { @Published var currentUrl = "" @Published var urlToLoad: URL? // 用于触发WebView加载新URL static func stripHttps(_ url: String) -> String { return url.replacingOccurrences(of: "^https?://", with: "", options: .regularExpression) } func updateUrl(_ inputUrl: String) { let fullUrl = inputUrl.hasPrefix("http") ? inputUrl : "https://\(inputUrl)" urlToLoad = URL(string: fullUrl) } }
步骤3:ContentView直接绑定ViewModel状态
去掉独立的@State var text,直接绑定ViewModel的currentUrl:
struct ContentView: View { @StateObject private var webModel = WebStateModel() var body: some View { VStack(spacing: 8) { TextField("输入URL", text: Binding( get: { webModel.currentUrl }, set: { webModel.currentUrl = WebStateModel.stripHttps($0) } ), onCommit: { webModel.updateUrl(webModel.currentUrl) }) .textFieldStyle(.roundedBorder) .padding(.horizontal) WebView(viewModel: webModel) .frame(maxWidth: .infinity, maxHeight: .infinity) } } }
步骤4:完善WebView与Coordinator的关联
确保WebView创建时将实例传给Coordinator监听:
struct WebView: NSViewRepresentable { @ObservedObject var viewModel: WebStateModel func makeNSView(context: Context) -> WKWebView { let webView = WKWebView() webView.navigationDelegate = context.coordinator webView.uiDelegate = context.coordinator // 让Coordinator监听这个实际使用的WebView context.coordinator.setupUrlObservation(for: webView) return webView } func updateNSView(_ nsView: WKWebView, context: Context) { // 响应ViewModel的加载请求 if let url = viewModel.urlToLoad, nsView.url != url { nsView.load(URLRequest(url: url)) viewModel.urlToLoad = nil // 避免重复加载 } } func makeCoordinator() -> Coordinator { Coordinator(viewModel) } }
可选:用WKNavigationDelegate替代KVO
KVO可能在URL跳转过程中触发多次,改用didFinish回调更稳定:
extension Coordinator { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { if let urlStr = webView.url?.absoluteString { viewModel.currentUrl = urlStr } } }
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

