Добавление жеста перетаскивания к изображению внутри 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 }
)
Вы можете прочитать больше об этом здесь: Изменение состояния во время обновления представления