Here is the code:
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)
}
}
}
}
}
Why when button "Add 1" or "Add 2" is tapped the related label is updated, but the views created from @ViewBuilder already not? How can I make them updated also?
This behavior occurs because of how viewModel.builder is stored and used. Specifically:
Why doesn't the @ViewBuilder content update? When you do this:
You're capturing the current value of value at the time viewBuilder is called. This means the closure doesn't reactively update when value changes later on, because it's outside the SwiftUI view lifecycle and doesn't have access to @State.
Also, viewModel.builder is stored as a plain closure, and the content returned from it is embedded in AnyView, which erases view identity and state tracking — so SwiftUI cannot diff or re-render it.
Try this:
Option 1:
viewModel.builder?(DemoModel(text: "World! \(value)"))
Option 2:
viewModel.builder?(DemoModel(text: "World! \(value)"))
Example:
struct DemoView: View {
let builder: ((DemoModel) -> AnyView)
var body: some View {
VStack {
builder(DemoModel(text: "World!"))
builder(DemoModel(text: "World2!"))
builder(DemoModel(text: "World3!"))
}
}
}