You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题根源

  1. WebView实例不匹配:Coordinator的init方法里重新创建了WKWebView,导致你监听的是一个从未被添加到界面的空实例,实际加载页面的WebView并没有被监听。
  2. 状态未绑定:Coordinator的@Published var text没有和ContentView的@State var text建立关联,即使Coordinator的text更新,ContentView也无法感知。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 08:39:46