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

如何在关联@State变化时更新@ViewBuilder创建的视图?

struct DemoModel {
    var text: String
}

struct DemoView: View {
    @State var viewModel = DemoViewModel()
    var body: some View {
        VStack {
            Text("Hello, ")
            viewModel.builder?(DemoModel(text: "World!"))
            viewModel.builder?(DemoModel(text: "World2!"))
            viewModel.builder?(DemoModel(text: "World3!"))
        }
    }
    
    func viewBuilder<V: View>(@ViewBuilder builder: @escaping (DemoModel) -> V) -> Self {
        viewModel.builder = { model in
            AnyView(builder(model))
        }
        return self
    }
}

class DemoViewModel {
    var builder: ((DemoModel) -> AnyView)? = nil
}

struct WrapperDemoView: View {
    @State var value = "🏆"
    var body: some View {
        VStack(spacing: 10) {
            HStack {
                Button("ADD 1") {
                    value += "1"
                }
                Button("ADD 2") {
                    value += "2"
                }
            }
            Text(value)
            DemoView()
                .viewBuilder { model in
                    HStack {
                        Text(model.text + " " + value)
                    }
                }
        }
    }
}

问题原因

  1. 视图刷新触发机制缺失:DemoViewModel是普通类,未遵循ObservableObject协议,也没有用@Published标记builder属性。当WrapperDemoView的value变化后,虽然会重新调用viewBuilder更新viewModel.builder,但DemoView无法感知到viewModel内部的属性变化,因此不会刷新Body重新渲染闭包内的视图。
  2. 闭包值捕获的时效性问题:最初设置的builder闭包捕获的是value的旧值,后续value更新时,旧闭包不会自动捕获新值;即便重新生成闭包并赋值,由于DemoViewModel不是可观察对象,DemoView依然无法触发刷新。

解决方法

通过改造视图模型的可观察性,让视图能感知到变化并同步刷新:

1. 将DemoViewModel改为可观察对象

让DemoViewModel遵循ObservableObject,并将builder标记为@Published,这样属性变化时会自动发送通知触发视图刷新:

class DemoViewModel: ObservableObject {
    @Published var builder: ((DemoModel) -> AnyView)? = nil
}

2. 用@StateObject持有视图模型

在DemoView中,将@State替换为@StateObject(iOS14+、macOS11+可用,是管理视图内部ObservableObject的标准方式),确保视图能监听viewModel的变化:

struct DemoView: View {
    @StateObject var viewModel = DemoViewModel() // 替换原@State
    var body: some View {
        VStack {
            Text("Hello, ")
            viewModel.builder?(DemoModel(text: "World!"))
            viewModel.builder?(DemoModel(text: "World2!"))
            viewModel.builder?(DemoModel(text: "World3!"))
        }
    }
    
    func viewBuilder<V: View>(@ViewBuilder builder: @escaping (DemoModel) -> V) -> Self {
        viewModel.builder = { model in
            AnyView(builder(model))
        }
        return self
    }
}

修改完成后,点击按钮更新value时,WrapperDemoView的Body会重建,重新调用viewBuilder生成捕获新value的闭包并赋值给viewModel.builder。由于builder是@Published属性,DemoView会收到通知刷新Body,从而渲染出包含最新value的视图。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:48:15