I have a basic scroll view in SwiftUI that has stackable cards. The cards can be switched by swiping on them. Sometimes scrolls on the scroll view can be mistaken as drags on the draggable view. In this case the top card is dragged to somewhere else on the screen, but the onEnded
doesn't fire and the card remains stuck in that position without returning back to the stack.
How can I fix the issue where scrolls on the scroll view are mistaken as drags on this sub view?
import SwiftUI
struct tetfqwe: View {
@State var cards: [String] = ["1", "2", "3", "4", "5"]
@State var offset: CGSize = .zero
var body: some View {
ScrollView {
Color.blue.frame(height: 200)
LazyVStack {
ZStack {
ForEach(cards.indices, id: \.self) { index in
Color.red.frame(width: 120, height: 90).cornerRadius(15, corners: .allCorners)
.overlay(content: {
RoundedRectangle(cornerRadius: 15)
.stroke(.black, lineWidth: 1.0)
})
.overlay(content: {
Text("\(index)")
})
.onTapGesture {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
.offset(x: Double(((cards.count - 1) - index) * 3))
.offset(x: index == (cards.count - 1) ? offset.width : 0.0, y: index == (cards.count - 1) ? offset.height : 0.0)
.highPriorityGesture(DragGesture()
.onChanged({ value in
if cards.count > 1 {
offset = value.translation
}
})
.onEnded({ value in
if cards.count > 1 {
if abs(value.translation.width) > 45.0 {
ended()
} else {
withAnimation(.bouncy(duration: 0.3)){
offset = .zero
}
}
}
})
)
}
}
}
Color.orange.frame(height: 1000).padding(.top, 100)
Color.green.frame(height: 1000)
Color.gray.frame(height: 1000)
}
}
func ended(){
withAnimation(.bouncy(duration: 0.3)){
let element = cards.removeLast()
cards.insert(element, at: 0)
offset = .zero
}
}
}
#Preview {
tetfqwe()
}
Do this on top side of your struct:
struct tetfqwe: View {
@State var cards: [String] = ["1", "2", "3", "4", "5"]
@State var offset: CGSize = .zero
@State var shouldScroll: Bool // add this to your code
var axes: Axis.Set { // add this to your code
return shouldScroll ? .vertical : []
}
And towards the bottom, do this:
.highPriorityGesture(DragGesture()
.onChanged({ value in
shouldScroll = false // add this to your code
if cards.count > 1 {
offset = value.translation
}
})
.onEnded({ value in
shouldScroll = true // add this to your code
if cards.count > 1 {
This solution simply disables the scrollView while the drag is in action '.onChanged' and re-enables it when drag gesture 'onEnded' ends.