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

SwiftUI中UIViewRepresentable的Coordinator如何自动同步viewModel?

解答

结论

不存在完全无需额外代码的「自动同步」方案,但有两种更符合SwiftUI设计规范的实现方式,可以避免手动赋值的麻烦:


方案1:移除Coordinator内部的ViewModel存储

你当前的Coordinator已经持有了SwiftUIWebView的parent引用,而parent的viewModel是@ObservedObject属性,外部传入新实例时会自动更新。你完全不需要在Coordinator内部单独存储一份ViewModel,所有需要访问ViewModel的场景直接通过parent.viewModel获取即可,天然就是最新值,无需任何同步操作。

同时需要修正你现有代码的一个严重问题:不要在UIViewRepresentable结构体中存储WKWebView作为属性,UIViewRepresentable是值类型,每次视图更新都会生成新的结构体实例,存储的WKWebView会被反复创建,导致状态丢失和内存泄漏,正确写法是在makeUIView中创建WKWebView。

修改后的代码示例:

struct SwiftUIWebView : UIViewRepresentable {
    @ObservedObject var viewModel: WebViewModel
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        print("SwiftUIWebView MAKE")
        let webView = WKWebView()
        // 直接在makeUIView中设置KVO,无需Coordinator存viewModel
        context.coordinator.estimatedProgressObserver = webView.observe(\.estimatedProgress, options: [.new]) { [weak self] webView, _ in
            guard let self = self else { return }
            // 直接访问当前结构体的viewModel,永远是最新实例
            self.viewModel.progress = webView.estimatedProgress
        }
        if let url = URL(string: viewModel.link) {
            webView.load(URLRequest(url: url))
        }
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        // 这里不需要再做ViewModel同步
        // 可选:如果需要响应link变化自动加载新网页,可以在这里加判断
        if uiView.url?.absoluteString != viewModel.link, let url = URL(string: viewModel.link) {
            uiView.load(URLRequest(url: url))
        }
    }
}

// Coordinator简化后代码
class Coordinator: NSObject {
    var parent: SwiftUIWebView
    var estimatedProgressObserver: NSKeyValueObservation?

    init(_ parent: SwiftUIWebView) {
        print("Coordinator init")
        self.parent = parent
        super.init()
    }

    deinit {
        estimatedProgressObserver = nil
    }
}

方案2:从根源避免生成新的ViewModel实例

你遇到的同步问题本质是每次redraw状态变化时,都在body内直接新建了WebViewModel实例,这不符合SwiftUI的状态管理规范。使用@StateObject持有ViewModel可以保证它只会初始化一次,不会因为视图重绘生成新实例,自然不存在同步问题。

修改后的WebContentViewTest代码示例:

struct WebContentViewTest : View {
    @Binding var redraw:Bool
    // 用@StateObject持有ViewModel,生命周期和当前View绑定,只会初始化一次
    @StateObject private var webModel = WebViewModel(progress: 0.0, link: "https://www.google.com")

    var body: some View {
        let _ = print("WebContentViewTest body")
        NavigationView {
            ZStack(alignment: .topLeading) {
                if redraw {
                    WebViewContainer(model: webModel)
                }
                VStack {
                    Button(action: {
                        redraw.toggle()
                        // 如果需要切换网页,直接修改model的link属性即可,无需新建实例
                        // webModel.link = "https://www.apple.com"
                    }) {
                        Text("redraw")
                    }
                    Spacer()
                }
            }
            .navigationBarTitle("Test Modal", displayMode: .inline)
        }
    }
}

补充说明

如果你的业务场景确实需要传入不同的ViewModel实例(比如复用Web容器加载不同来源的网页),你当前使用的在updateUIView中手动赋值context.coordinator.viewModel = viewModel的写法,就是Apple官方推荐的标准实现,性能开销可以忽略,完全不需要刻意规避。


内容的提问来源于stack exchange,提问作者zumzum

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 14:06:10