Добавление жеста перетаскивания к изображению внутри ForEach в SwiftUI

В настоящее время я разрабатываю приложение, которое показывает пользователю его ящики с растениями и некоторые измерения (например, влажность почвы, температуру и т. Д.). В каждом ящике с растениями есть несколько растений внутри, которые отображаются в подробном виде. Я хочу, чтобы пользователь мог перетащить одно из растений в угол, чтобы удалить его. В настоящее время у меня есть такая реализация:

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

}

Это выглядит так: Снимок экрана фрагмента кода

Однако, когда я начинаю перетаскивать, оба изображения перемещаются. Я думаю, это потому, что смещение обоих изображений привязано к одной и той же переменной (DragOffset). Как сделать так, чтобы двигалось только одно изображение?

Я думал о реализации какого-то массива, который отображает индекс растения на определенное смещение, но я не знал, как реализовать это в жесте. Спасибо за любую помощь!

2 ответа

Решение

Возможный подход - сохранить выделение во время перетаскивания. Протестировано с 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 выполнил свою работу, но мне пришлось обновить его для Xcode 13, чтобы он не выдавал «Изменение состояния во время обновления представления, это вызовет неопределенное поведение».

В основном .updatingмодификатор постоянно обновляет @state var selectedчто приводит к обновлению всего представления, в то время как мы пытаемся изменить для него новое значение, оно вызывает неопределенное поведение.

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

Вы можете прочитать больше об этом здесь: Изменение состояния во время обновления представления

Другие вопросы по тегам