如何在关联@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) } } } } }
问题原因
- 视图刷新触发机制缺失:
DemoViewModel是普通类,未遵循ObservableObject协议,也没有用@Published标记builder属性。当WrapperDemoView的value变化后,虽然会重新调用viewBuilder更新viewModel.builder,但DemoView无法感知到viewModel内部的属性变化,因此不会刷新Body重新渲染闭包内的视图。 - 闭包值捕获的时效性问题:最初设置的
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
相关产品推荐
相关产品推荐

