如何使用 SwiftUI 在视图上检测向上、向下、向左和向右滑动

How to detect Swiping UP, DOWN, LEFT and RIGHT with SwiftUI on a View

我开始构建 Apple Watch 应用程序。

我目前正在做的工作需要我在四个主要方向(UPDOWNLEFTRIGHT 上使用检测滑动)

问题是我不知道如何检测到这一点。我一直在环顾四周,我已经走到死胡同了。

当用户在视图上滑动 UP 时,我可以对下面的视图做些什么来打印 swiped up

struct MyView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

谢谢。

你可以使用 DragGesture

.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local)
                    .onEnded({ value in
                        if value.translation.width < 0 {
                            // left
                        }

                        if value.translation.width > 0 {
                            // right
                        }
                        if value.translation.height < 0 {
                            // up
                        }

                        if value.translation.height > 0 {
                            // down
                        }
                    }))

如果你想要一个对滑动方向性更“宽容”的,你可以使用更多的条件来帮助平衡它:

编辑:做了一些更多的测试,显然第二个条件的值增加了一些混乱,所以我调整了它们以消除上述混乱并使手势防弹(拖动到角落现在会出现“没有线索”而不是其中一种手势)...

let detectDirectionalDrags = DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
.onEnded { value in
    print(value.translation)
    
    if value.translation.width < 0 && value.translation.height > -30 && value.translation.height < 30 {
        print("left swipe")
    }
    else if value.translation.width > 0 && value.translation.height > -30 && value.translation.height < 30 {
        print("right swipe")
    }
    else if value.translation.height < 0 && value.translation.width < 100 && value.translation.width > -100 {
        print("up swipe")
    }
    else if value.translation.height > 0 && value.translation.width < 100 && value.translation.width > -100 {
        print("down swipe")
    }
    else {
        print("no clue")
    }

由于其他解决方案在物理设备上有点不一致,我决定想出另一种似乎在不同屏幕尺寸上更加一致的解决方案,因为除了 [=11= 之外没有硬编码值].

.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
            .onEnded { value in
                let horizontalAmount = value.translation.width as CGFloat
                let verticalAmount = value.translation.height as CGFloat
                
                if abs(horizontalAmount) > abs(verticalAmount) {
                    print(horizontalAmount < 0 ? "left swipe" : "right swipe")
                } else {
                    print(verticalAmount < 0 ? "up swipe" : "down swipe")
                }
            })

根据本杰明的回答,这是一种更快捷的处理方式

.gesture(DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
    .onEnded { value in
        print(value.translation)
        switch(value.translation.width, value.translation.height) {
            case (...0, -30...30):  print("left swipe")
            case (0..., -30...30):  print("right swipe")
            case (-100...100, ...0):  print("up swipe")
            case (-100...100, 0...):  print("down swipe")
            default:  print("no clue")
        }
    }
)

这个响应更快:

.gesture(DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
            .onEnded { value in
                let direction = atan2(value.translation.width, value.translation.height)
                switch direction {
                case (-Double.pi/4..<Double.pi/4): self.playMove(.down)
                case (Double.pi/4..<Double.pi*3/4): self.playMove(.right)
                case (Double.pi*3/4...Double.pi), (-Double.pi..<(-Double.pi*3/4)):
                    self.playMove(.up)
                case (-Double.pi*3/4..<(-Double.pi/4)): self.playMove(.left)
                default: 
                    print("unknown)")
                }
            }

为了简单起见,我会创建一个修饰符。用法将如下所示:

yourView
        .onSwiped(.down) {
            // Action for down swipe
        }

yourView
        .onSwiped { direction in 
            // React to detected swipe direction
        }

您还可以使用 trigger 参数来配置接收更新:连续或仅在手势结束时。

完整代码如下:

struct SwipeModifier: ViewModifier {
    enum Directions: Int {
        case up, down, left, right
    }

    enum Trigger {
        case onChanged, onEnded
    }

    var trigger: Trigger
    var handler: ((Directions) -> Void)?

    func body(content: Content) -> some View {
        content.gesture(
            DragGesture(
                minimumDistance: 24,
                coordinateSpace: .local
            )
            .onChanged {
                if trigger == .onChanged {
                    handle([=12=])
                }
            }.onEnded {
                if trigger == .onEnded {
                    handle([=12=])
                }
            }
        )
    }

    private func handle(_ value: _ChangedGesture<DragGesture>.Value) {
        let hDelta = value.translation.width as CGFloat
        let vDelta = value.translation.height as CGFloat

        if abs(hDelta) > abs(vDelta) {
            handler?(hDelta < 0 ? .left : .right)
        } else {
            handler?(vDelta < 0 ? .up : .down)
        }
    }
}

extension View {
    func onSwiped(
        trigger: SwipeModifier.Trigger = .onChanged,
        action: @escaping (SwipeModifier.Directions) -> Void
    ) -> some View {
        let swipeModifier = SwipeModifier(trigger: trigger) {
            action([=12=])
        }
        return self.modifier(swipeModifier)
    }
    func onSwiped(
        _ direction: SwipeModifier.Directions,
        trigger: SwipeModifier.Trigger = .onChanged,
        action: @escaping () -> Void
    ) -> some View {
        let swipeModifier = SwipeModifier(trigger: trigger) {
            if direction == [=12=] {
                action()
            }
        }
        return self.modifier(swipeModifier)
    }
}