I was playing around with @resultBuilder
and made a simple result builder called SumBuilder
which takes a bunch of integers and adds them all up.
@resultBuilder
struct SumBuilder {
static func buildBlock(_ components: Int...) -> Int {
components.reduce(0, )
}
}
Since I was also playing around with shapes the other day I thought about creating a custom shape which can use this new result builder in its implementation. The custom shape just draws a line from the origin of the container to the point specified.
struct CustomShape: Shape {
@SumBuilder let sum: () -> Int
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: .zero)
path.addLine(to: CGPoint(x: sum(), y: sum()))
}
}
}
When I hooked up sum
to a @State
variable in my ContentView
it works perfectly. However, when I change sum
using .onAppear()
, the value of sum
will change under the hood but the View
doesn't update to reflect that:
struct ContentView: View {
@State private var distance = 0
var body: some View {
VStack {
Button("Increase distance") {
distance = 100
}
CustomShape { distance }
.stroke()
.onAppear { distance = 100 } // << Updates distance, but the View still doesn't show
// the line until I press the Button to make distance = 200
}
}
}
I tried moving the .onAppear()
to the Text
thinking the .onAppear()
wasn't being called since the CustomShape
is initially a dot, but that didn't change anything. What's even stranger is that when I transform the CustomShape
to a regular View
, the .onAppear()
works as intended.
Does anyone know how to make a Shape
which utilises @resultBuilder
update even when a value is changed using .onAppear()
? If you can help in any shape or form I'd be very grateful.
Thanks in advance!
CodePudding user response:
As i mentioned, i just tried your code but using a ViewModel, which worked as expected. In production you probably want to use input-ouput-pattern but this is the least amount of code to explain what i meant by using a ViewModel.
extension ContentView {
final class ViewModel: ObservableObject {
@Published var distance = 0
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
VStack {
Button("Increase distance") {
viewModel.distance = 100
}
CustomShape { viewModel.distance }
.stroke()
.onAppear {
viewModel.distance = 100
}
}
}
}
CodePudding user response:
In SwiftUI, the .onAppear()
modifier is used to perform some action when a view appears on the screen. However, this modifier does not trigger a re-render of the view, so any changes made within the .onAppear() closure will not be reflected in the view's appearance.
This applies to shapes that use @resultBuilder as well. If a shape is created using @resultBuilder and you make changes to it within the .onAppear()
closure, the changes will not be visible in the view. To update a shape that uses @resultBuilder you need to use .onChange()
or .onReceive()
instead of .onAppear()
to update the shape which will trigger the view to re-render.