'How to implement a left or right DragGesture() that trigger a switch case in SwiftUI?
I have created a DragGesture in a View that should select a @State(Bool) whether the user swipe left or right.
The thing is that only swiping right is detected.
How to use .gesture() to capture whether a user is swiping left or right on his screen?
import SwiftUI
struct SwiftUIView: View {
//MARK: Value to change on swipe gesture
@State var swipeRight: Bool
var body: some View {
VStack {
//MARK: Value displayed after user swiped
Text($swipeRight ? "Right" : "Left")
}
.gesture(
DragGesture()
.onChanged { (value) in
//MARK: What is it missing here?
switch value.location.x {
case ...(-0.5):
self.swipeRight = false
print("Swipe Left return false")
case 0.5...:
self.swipeRight = true
print("Swipe Right return true")
default: ()
}
})
}
Solution 1:[1]
You should compare old and new locations instead:
if value.startLocation.x > value.location.x {
print("Swipe Left")
} else {
print("Swipe Right")
}
So the refactored version of your code would be:
struct ContentView: View {
enum SwipeHorizontalDirection: String {
case left, right, none
}
@State var swipeHorizontalDirection: SwipeHorizontalDirection = .none { didSet { print(swipeHorizontalDirection) } }
var body: some View {
VStack {
Text(swipeHorizontalDirection.rawValue)
}
.gesture(
DragGesture()
.onChanged {
if $0.startLocation.x > $0.location.x {
self.swipeHorizontalDirection = .left
} else if $0.startLocation.x == $0.location.x {
self.swipeHorizontalDirection = .none
} else {
self.swipeHorizontalDirection = .right
}
})
}
}
Solution 2:[2]
Swift 5, iOS 13
An improved version presented by [Mojtaba Hosseini ][1] here.
[1]: https://stackoverflow.com/users/5623035/mojtaba-hosseini. Place the enum and function before the body of ContentView.
enum SwipeHVDirection: String {
case left, right, up, down, none
}
func detectDirection(value: DragGesture.Value) -> SwipeHVDirection {
if value.startLocation.x < value.location.x - 24 {
return .left
}
if value.startLocation.x > value.location.x + 24 {
return .right
}
if value.startLocation.y < value.location.y - 24 {
return .down
}
if value.startLocation.y > value.location.y + 24 {
return .up
}
return .none
}
...
With it called inside a DragGesture. Calling it on onEnded to stop it firing multiple times.
.gesture(DragGesture()
.onEnded { value in
print("value ",value.translation.width)
let direction = self.detectDirection(value: value)
if direction == .left {
// your code here
}
}
)
Obviously you need/can add other directions as needed...
Solution 3:[3]
I think the directions above were inverted? Or I'm misunderstanding swipe left and right. Anyway, here's a further refinement using view modifiers. You can just at .onSwipe { direction in ... } to your views.
struct SwipeModifier: ViewModifier {
let action: ((UISwipeGestureRecognizer.Direction) -> Void)?
init(perform action: ((UISwipeGestureRecognizer.Direction) -> Void)? = nil) {
self.action = action
}
func body(content: Content) -> some View {
content
.gesture(DragGesture(minimumDistance: 24.0, coordinateSpace: .local)
.onEnded { value in
guard let action = action else {
return
}
if value.startLocation.x > value.location.x {
action(.left)
} else if value.startLocation.x < value.location.x {
action(.right)
} else if value.startLocation.y > value.location.y {
action(.down)
} else if value.startLocation.y < value.location.y {
action(.up)
}
})
}
}
extension View {
public func onSwipe(perform action: ((UISwipeGestureRecognizer.Direction) -> Void)? = nil) -> some View {
return self.modifier(SwipeModifier(perform: action))
}
}
Solution 4:[4]
Extend DragGesture.Value
:
import SwiftUI
extension DragGesture.Value {
func detectDirection(_ tolerance: Double = 24) -> Direction? {
if startLocation.x < location.x - tolerance { return .left }
if startLocation.x > location.x + tolerance { return .right }
if startLocation.y > location.y + tolerance { return .up }
if startLocation.y < location.y - tolerance { return .down }
return nil
}
enum Direction {
case left
case right
case up
case down
}
}
Add a dismissingGesture method to View
:
extension View {
public func dismissingGesture(tolerance: Double = 24, action: @escaping () -> ()) -> some View {
gesture(DragGesture()
.onEnded { value in
let direction = value.detectDirection(tolerance)
if direction == .left {
action()
}
}
)
}
}
Example usage:
struct DismissingGesture_Previews: PreviewProvider {
static var previews: some View {
Preview()
}
struct Preview: View {
var body: some View {
NavigationView {
NavigationLink {
Destination()
} label: {
Text("Go")
}
}
}
}
struct Destination: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
Text("Nothing")
}
.dismissingGesture {
dismiss()
}
.navigationBarHidden(true)
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | lukembrowne |
Solution 3 | Luke Howard |
Solution 4 | emehex |