The complex thing is to allow both scroll and move.
I do it by using longPress for move and normal press for scrolling.
Here is a code snippet:
struct ContentView: View {
struct Item: Identifiable {
let id = UUID()
var pos: Int
var value: String
var color: Color
var onMove: Bool = false
}
@State var items : [Item] = [
Item(pos: 0, value: "A", color: .blue),
Item(pos: 1, value: "B", color: .red),
Item(pos: 2, value: "C", color: .blue),
Item(pos: 3, value: "D", color: .red),
Item(pos: 4, value: "E", color: .blue),
Item(pos: 5, value: "F", color: .red),
Item(pos: 6, value: "G", color: .blue),
Item(pos: 7, value: "H", color: .red),
Item(pos: 8, value: "I", color: .blue),
Item(pos: 9, value: "J", color: .red),
Item(pos: 10, value: "K", color: .blue)]
@GestureState private var isDetectingLongPress = false
@State private var completedLongPress = false
@State private var activeLongPress = false
var longPress: some Gesture {
LongPressGesture(minimumDuration: 0.5) // LongPress to move, shortpress to scroll
.updating($isDetectingLongPress) { currentState, gestureState, transaction in
gestureState = currentState
activeLongPress = true
}
.onEnded { finished in
self.activeLongPress = !finished
}
}
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 5) {
ForEach(items) { item in
Rectangle()
.fill(item.onMove ? .green : item.color)
.frame(width:40, height:40)
.overlay {
Text("\(item.value)")
}
.gesture(
DragGesture(minimumDistance: 2)
.onChanged { _ in
items[item.pos].onMove = true
}
.onEnded { value in
let shift = Int(value.translation.width / 45) // 40 width + 5 interspace
let newPos = item.pos+shift
if newPos >= 0 && newPos <= 7 {
let element = items.remove(at: item.pos)
items.insert(element, at: newPos)
items[newPos].onMove = false
for itemPos in 0...7 {
items[itemPos].pos = itemPos
}
}
}
)
.gesture(longPress)
}
}
}
.scrollDisabled(activeLongPress)
}
}