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

Bar*_*yle 23 swipe apple-watch swiftui

我正在构建 Apple Watch 应用程序。

什么我目前工作的需要我利用在四个主要方向检测刷卡(UPDOWNLEFTRIGHT

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

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

struct MyView: View {
    var body: some View {
        Text("Hello, World!")
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢。

vad*_*ian 42

根据本杰明的回答,这是处理案件的更快捷的方法

.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")
        }
    }
)
Run Code Online (Sandbox Code Playgroud)


Sor*_*ica 34

你可以用 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
                        }
                    }))
Run Code Online (Sandbox Code Playgroud)

  • 这看起来不错,但无法正常工作。除非你是机器人,否则几乎不可能不产生多个响应。 (4认同)

Ben*_* B. 20

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

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

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")
    }
Run Code Online (Sandbox Code Playgroud)

  • 我感到震惊的是,定义这样的行为需要看起来非常明确的构造,而且构造容易出错。在横向和纵向上滑动是否“感觉良好”?这看起来像是 API 中的一个大漏洞。 (3认同)

小智 12

由于其他解决方案在物理设备上有点不一致,我决定提出另一个在不同屏幕尺寸上似乎更加一致的解决方案,因为除了minimumDistance.

.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")
                }
            })
Run Code Online (Sandbox Code Playgroud)


小智 9

创建扩展:

extension View {
    func swipe(
        up: @escaping (() -> Void) = {},
        down: @escaping (() -> Void) = {},
        left: @escaping (() -> Void) = {},
        right: @escaping (() -> Void) = {}
    ) -> some View {
        return self.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() }
            }))
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

            Image() // or any View
                .swipe(
                    up: {
                        // action for up
                    },
                    right: {
                        // action for right
                    })
Run Code Online (Sandbox Code Playgroud)

请注意,每个方向都是可选参数


小智 5

这更具响应性:

\n
.gesture(\n    DragGesture()\n        .onEnded { value in\n            \n            let pi = Double.pi\n            \n            let direction = atan2(value.translation.width, value.translation.height)\n            \n            switch direction {\n            case (-pi/4..<pi/4): print("down swipe")\n            case (pi/4..<pi*3/4): print("right swipe")\n            case (pi*3/4...pi), (-pi..<(-pi*3/4)):\n                print("up swipe")\n            case (-pi*3/4..<(-pi/4)): print("left swipe")\n            default:\n                print("no clue")\n            }\n        }\n
Run Code Online (Sandbox Code Playgroud)\n

解释:

\n
    \n
  • 拖动手势返回的变化向量为value.translation
  • \n
  • 然后该方法使用atan2查找该向量的方向,如下所示
  • \n
\n

根据value.translation返回方式,以下值atan2将如下:

\n
    \n
  • \xcf\x80将匹配理想的“向上”手势
  • \n
  • -\xcf\x80/2是一个理想的“左”手势
  • \n
  • 0.0将匹配理想的“向下”手势
  • \n
  • \xcf\x80/2是一个理想的“正确”手势
  • \n
\n

现在我们想将圆分成 4 个四分之一,每个四分之一包括上面中间的每个值。例如,(-\xcf\x80/4...\xcf\x80/4)将包括可被视为“向下”方向的近似值的任何值。

\n


Kon*_*hin 5

为了简单起见,我会创建一个修改器。用法如下:

yourView
        .onSwiped(.down) {
            // Action for down swipe
        }
Run Code Online (Sandbox Code Playgroud)

或者

yourView
        .onSwiped { direction in 
            // React to detected swipe direction
        }
Run Code Online (Sandbox Code Playgroud)

您还可以使用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($0)
                }
            }.onEnded {
                if trigger == .onEnded {
                    handle($0)
                }
            }
        )
    }

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

        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($0)
        }
        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 == $0 {
                action()
            }
        }
        return self.modifier(swipeModifier)
    }
}
Run Code Online (Sandbox Code Playgroud)