iosswiftswiftuigesturedraggesture

Adding Drag Gesture to image inside a ForEach in SwiftUI


I am currently developing an App shows a user his plant boxes and some measurements (like ground humidity, temperature etc) Each plant box has some plants inside it, which are being displayed in a detail view. I want the user to be able to drag one of the plants to a corner in order to remove it. Currently I have this implementation:

@GestureState private var dragOffset = CGSize.zero

var body: some View {

    //Plants Headline
    VStack (spacing: 5) {
        Text("Plants")
            .font(.headline)
            .fontWeight(.light)

        //HStack for Plants Images
        HStack (spacing: 10) {

            //For each that loops through all of the plants inside the plant box
            ForEach(plantBoxViewModel.plants) { plant in

                //Image of the individual plant (obtained via http request to the backend)
                Image(base64String: plant.picture)
                    .resizable()
                    .clipShape(Circle())
                    .overlay(Circle().stroke(Color.white, lineWidth: 2))
                    .offset(x: self.dragOffset.width, y: self.dragOffset.height)
                    .animation(.easeInOut)
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 70, height: 70)
                    .gesture(
                           DragGesture()
                            .updating(self.$dragOffset, body: { (value, state, transaction) in
                               state = value.translation
                           })
                   )
            }
        }
    }

}

It looks like the following: Screenshot of Code Snippet

However, when I start dragging, both images move. I think it is because the offset of both images gets bound to the same variable (DragOffset). How can I accomplish that only 1 image is moving?

I thought about implementing some sort of array that maps the index of the plant to a specific offset but I wouldn't know how to implement it into the gesture. Thanks for any help!


Solution

  • Here is possible approach - to keep selection during drag. Tested with Xcode 11.4 / iOS 13.4.

    demo

    @GestureState private var dragOffset = CGSize.zero
    @State private var selected: Plant? = nil // << your plant type here
    var body: some View {
    
        //Plants Headline
        VStack (spacing: 5) {
            Text("Plants")
                .font(.headline)
                .fontWeight(.light)
    
            //HStack for Plants Images
            HStack (spacing: 10) {
    
                //For each that loops through all of the plants inside the plant box
                ForEach(plantBoxViewModel.plants) { plant in
    
                    //Image of the individual plant (obtained via http request to the backend)
                    Image(base64String: plant.picture)
                        .resizable()
                        .clipShape(Circle())
                        .overlay(Circle().stroke(Color.white, lineWidth: 2))
                        .offset(x: self.selected == plant ? self.dragOffset.width : 0,
                                y: self.selected == plant ? self.dragOffset.height : 0)
                        .animation(.easeInOut)
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 70, height: 70)
                        .gesture(
                            DragGesture()
                                .updating(self.$dragOffset, body: { (value, state, transaction) in
                                    if nil == self.selected {
                                        self.selected = plant
                                    }
                                    state = value.translation
                                }).onEnded { _ in self.selected = nil }
                    )
                }
            }
        }
    
    }