Home > Blockchain >  .onAppear() doesn't update Shapes that use @resultBuilder
.onAppear() doesn't update Shapes that use @resultBuilder

Time:01-24

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.

  • Related