简体   繁体   中英

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!

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

演示

@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 }
                )
            }
        }
    }

}

@Asperi's answer did the job but I had to update it for Xcode 13 so it doesn't throw "Modifying state during view update, this will cause undefined behavior".

Basically the .updating modifier is constantly updating the @state var selected which causes the entire view to refresh, while we attempt to affect a new value to it, it throws the undefined behaviour.

.gesture(
                                DragGesture()
                                    .onChanged { value in
                                        // Updated
                                        if nil == self.selected {
                                            self.selected = plant
                                        }
                                    }
                                    .updating(self.$dragOffset, body: { (value, state, transaction) in
                                        // This part is removed for causing undefined behavior
                                        // if nil == self.selected {
                                        //     self.selected = plant
                                        // }
                                        state = value.translation
                                    }).onEnded { _ in self.selected = nil }
                            )

You can read more about it here: Modifying state during view update

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