I have a view where each list item has leading/trailing swipe actions. When I add a Drag gesture recognizer to the view the swipe actions become very hard to trigger. I want to add a drag gesture to the view to allow custom edge (left or right) swipes to navigate to a different view. I only really need this drag gesture to recognize on the left and right edges of the screen (this is what the gray colors represent). How can I make the swipe actions trigger normally while having the gesture for edge swipes?
import SwiftUI
struct swipeTest: View {
@State var offset = 0.0
var body: some View {
VStack {
List {
ForEach(0..<60, id: \.self) { _ in
RoundedRectangle(cornerRadius: 10)
.frame(height: 50).foregroundStyle(.blue).padding(15)
.swipeActions(edge: .trailing) {
Button(action: {
}, label: {
Image(systemName: "bell")
}).tint(.orange)
}
.swipeActions(edge: .leading) {
Button(action: {
}, label: {
Image(systemName: "pin")
}).tint(.green)
}
}
}.listStyle(.plain)
}
.offset(x: offset)
.overlay(alignment: .leading, content: {
// Only need swipe on very left edge
Color.gray.opacity(0.3).frame(width: 15)
})
.overlay(alignment: .trailing, content: {
// Only need swipe on very right edge
Color.gray.opacity(0.3).frame(width: 15)
})
.simultaneousGesture (
DragGesture()
.onChanged({ value in
let width = widthScreen()
if value.startLocation.x < 15.0 || value.startLocation.x > (width - 15.0) {
offset = value.translation.width
}
})
.onEnded({ value in
offset = 0.0
})
)
}
}
func widthScreen() -> CGFloat {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
return window?.screen.bounds.width ?? 0
}
#Preview {
swipeTest()
}
You're already added gray overlays to indicate where the custom drag gesture should activate, so just add the gesture to those Color.gray
s!
Of course, in your real code you might want to use a Color.clear
, to hide that region from the user. Here is an example:
struct SwipeTest: View {
@GestureState var offset = 0.0
var body: some View {
VStack {
List {
ForEach(0..<60, id: \.self) { _ in
RoundedRectangle(cornerRadius: 10)
.frame(height: 50).foregroundStyle(.blue).padding(15)
.swipeActions(edge: .trailing) {
Button(action: {
}, label: {
Image(systemName: "bell")
}).tint(.orange)
}
.swipeActions(edge: .leading) {
Button(action: {
}, label: {
Image(systemName: "pin")
}).tint(.green)
}
}
}.listStyle(.plain)
}
.offset(x: offset)
.overlay(alignment: .leading) { dragRegion }
.overlay(alignment: .trailing) { dragRegion }
.animation(.default, value: offset)
}
var dragRegion: some View {
let gesture = DragGesture().updating($offset) { value, state, transaction in
state = value.translation.width
transaction.disablesAnimations = true
}
return Color.clear
.frame(width: 15)
.contentShape(Rectangle())
.gesture(gesture)
}
}
Note that I have changed offset
to a @GestureState
, since your code resets it to 0 after the gesture ends. I have also taken the liberty to add an animation when the offset bounces back. In your real code, you said you wanted to "navigate to a different view", but it's unclear just from your question how exactly you want to do that, so I'll leave that part up to you.