简体   繁体   中英

SwiftUI View not updating after @ObservedObject is changed

Intended Feature:

  1. Tap to add circle
  2. Press "next" to create new "frame"
  3. Drag circle to new position
  4. Press "back" to revert circle to previous position

Issue: As shown above, at the last part when I tap "back", the circle stays at dragged position instead of being reverted as intended.

eg When I add a circle to (0,0), create a new frame, drag the circle to a new location (10, 10) and tap "Back", the console prints " Frame: 0, position (0,0) ". And when I tap next, it prints " Frame: 1, position (10,10) ". And tapping "Back" prints (0,0) again. But the Circle position does not update.

I have tried using a class for the DraggableCircleModel struct and used @Published on its position but that didn't seem to work as well.

I provided my classes below to give some more context. Also this is only my second time posting a question here so any advice to improve my question would be appreciated. Thanks a bunch!

Back and Next buttons

Button(action: {
    self.viewModel.goTo(arrangementIndex: self.viewModel.currentIndex - 1)
}) { Text("Back") }

Button(action: {
    self.viewModel.goTo(arrangementIndex: self.viewModel.currentIndex + 1)
}) { Text("Next")}

View used to present the circles:

struct DisplayView: View {

@ObservedObject var viewModel: ViewModel

var body: some View {
    ZStack {
        Rectangle()
            .overlay(
                TappableView { location in
                    self.viewModel.addCircle(at: location)
            })
        ForEach(self.viewModel.currentArrangement.circles, id: \.id) { circle in
            return DraggableCircleView(viewModel: self.viewModel,
                       circleIndex: circle.circleIndex,
                       diameter: 50,
                       offset: circle.position)
        }
    }
}

}

Relevant parts of the DraggableCircle View

struct DraggableCircleView: View {
    init(viewModel: ViewModel, circleIndex: Int, diameter: CGFloat, offset: CGPoint) {
        // Initialize
        ...
        _viewState = /*State<CGSize>*/.init(initialValue: CGSize(width: offset.x, height: offset.y))

        // **Debugging print statement**
        print("\(self.viewModel.currentCircles.forEach{ print("Frame: \(self.viewModel.currentIndex), position \($0.position)") }) \n")
    }

    var body: some View {
    let minimumLongPressDuration = 0.0
    let longPressDrag = LongPressGesture(minimumDuration: minimumLongPressDuration)
        .sequenced(before: DragGesture())
        .updating($dragState) { value, state, transaction in
            // Update circle position during drag
            ...
        }
        .onEnded { value in
            guard case .second(true, let drag?) = value else { return }
            // get updated position of circle after drag
            ...
            self.viewModel.setPositionOfCircle(at: self.circleIndex, to: circlePosition)
        }
    
    return Circle()
        // Styling omitted
        ...
        .position(
            x: viewState.width + dragState.translation.width,
            y: viewState.height + dragState.translation.height
        )
        .gesture(longPressDrag)

}

Solved it. The issue lies with the.position(...) modifier of the DraggableCircle View. Previously, it was only reflecting the state of the DraggableCircle but was not updating based on the underlying ViewModel and Model.

Changing it to:

.position(
    x: self.viewModel.currentCircles[circleIndex].position.x + dragState.translation.width,
    y: self.viewModel.currentCircles[circleIndex].position.y + dragState.translation.height)

did the trick. This is because the position of the DraggableCircle now reflects the underlying ViewModel instead of just the state of the DraggableCircle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM