我开始学习开发苹果手表应用。
我目前正在开发的项目需要检测四个主要方向(上
,下
,左
和右
)的滑动操作。
问题是我不知道如何检测。我已经搜索过了,但却陷入了死胡同。
在下面的视图中,我该怎么做才能在用户向上
滑动视图时仅打印出swiped up
?
struct MyView: View {
var body: some View {
Text("Hello, World!")
}
}
感谢。我开始学习开发苹果手表应用。
我目前正在开发的项目需要检测四个主要方向(上
,下
,左
和右
)的滑动操作。
问题是我不知道如何检测。我已经搜索过了,但却陷入了死胡同。
在下面的视图中,我该怎么做才能在用户向上
滑动视图时仅打印出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
}
}))
minimumDistance
之外没有硬编码的值。.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
.onEnded { value in
let horizontalAmount = value.translation.width
let verticalAmount = value.translation.height
if abs(horizontalAmount) > abs(verticalAmount) {
print(horizontalAmount < 0 ? "left swipe" : "right swipe")
} else {
print(verticalAmount < 0 ? "up swipe" : "down swipe")
}
})
根据Benjamin的回答,这是一种处理这些情况更为迅速的方法。
.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")
}
}
)
如果您想得到更“宽容”的滑动方向,可以使用一些条件语句来帮助平衡:
编辑:进行了更多测试,显然第二个条件语句的值会产生一些混淆,因此我调整了它们以消除所述混淆并使手势无懈可击(拖动到角落现在将出现“无线索”而不是其中之一手势)...
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")
}
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() }
}))
}
}
那么:
Image() // or any View
.swipe(
up: {
// action for up
},
right: {
// action for right
})
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($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)
}
}
.gesture(
DragGesture()
.onEnded { value in
let pi = Double.pi
let direction = atan2(value.translation.width, value.translation.height)
switch direction {
case (-pi/4..<pi/4): print("down swipe")
case (pi/4..<pi*3/4): print("right swipe")
case (pi*3/4...pi), (-pi..<(-pi*3/4)):
print("up swipe")
case (-pi*3/4..<(-pi/4)): print("left swipe")
default:
print("no clue")
}
}
解释:
value.translation
atan2
来确定该向量的方向,如下所示根据value.translation
的返回值,atan2
的值将如下:
π
对应理想的"上"手势-π/2
对应理想的"左"手势0.0
对应理想的"下"手势π/2
对应理想的"右"手势现在,我们想将圆分成4个象限,每个象限中间包含上述的每个值。例如,(-π/4...π/4)
将包含任何可以被视为"下"方向的近似值。
可能有点晚了,但这里有另一种实现方式,它使用OptionSet使其使用方式更像其他各种SwiftUI组件 -
struct Swipe: OptionSet, Equatable {
init(rawValue: Int) {
self.rawValue = rawValue
}
let rawValue: Int
fileprivate var swiped: ((DragGesture.Value, Double) -> Bool) = { _, _ in false } // prevents a crash if someone creates a swipe directly using the init
private static let sensitivityFactor: Double = 400 // a fairly arbitrary figure which gives a reasonable response
static var left: Swipe {
var swipe = Swipe(rawValue: 1 << 0)
swipe.swiped = { value, sensitivity in
value.translation.width < 0 && value.predictedEndTranslation.width < sensitivity * sensitivityFactor
}
return swipe
}
static var right: Swipe {
var swipe = Swipe(rawValue: 1 << 1)
swipe.swiped = { value, sensitivity in
value.translation.width > 0 && value.predictedEndTranslation.width > sensitivity * sensitivityFactor
}
return swipe
}
static var up: Swipe {
var swipe = Swipe(rawValue: 1 << 2)
swipe.swiped = { value, sensitivity in
value.translation.height < 0 && value.predictedEndTranslation.height < sensitivity * sensitivityFactor
}
return swipe
}
static var down: Swipe {
var swipe = Swipe(rawValue: 1 << 3)
swipe.swiped = { value, sensitivity in
value.translation.height > 0 && value.predictedEndTranslation.height > sensitivity * sensitivityFactor
}
return swipe
}
static var all: Swipe {
[.left, .right, .up, .down]
}
private static var allCases: [Swipe] = [.left, .right, .up, .down]
fileprivate var array: [Swipe] {
Swipe.allCases.filter { self.contains($0) }
}
}
extension View {
func swipe(_ swipe: Swipe, sensitivity: Double = 1, action: @escaping (Swipe) -> ()) -> some View {
return gesture(DragGesture(minimumDistance: 30, coordinateSpace: .local)
.onEnded { value in
swipe.array.forEach { swipe in
if swipe.swiped(value, sensitivity) {
action(swipe)
}
}
}
)
}
}
HStack {
// content
}
.swipe([.left, .right]) { swipe in // could also be swipe(.left) or swipe(.all), etc
doSomething(with: swipe)
}
显然,检测滑动的逻辑有点基础,但很容易根据您的要求进行调整。
import SwiftUI
struct SwipeModifier: ViewModifier {
let rightToLeftAction: () -> ()
let leftToRightAction: () -> ()
func body(content: Content) -> some View {
content
.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
.onEnded { value in
let horizontalAmount = value.translation.width
let verticalAmount = value.translation.height
guard abs(horizontalAmount) > abs(verticalAmount) else {
return
}
withAnimation {
horizontalAmount < 0 ? rightToLeftAction() : leftToRightAction()
}
})
}
}
extension View {
func swipe(rightToLeftAction: @escaping () -> (), leftToRightAction: @escaping () -> ()) -> some View {
modifier(SwipeModifier(rightToLeftAction: rightToLeftAction, leftToRightAction: leftToRightAction))
}
}
改进版本的Fynn Becker的回答。
typealias SwipeDirectionAction = (SwipeDirection) -> Void
extension View {
/// Adds an action to perform when swipe end.
/// - Parameters:
/// - action: The action to perform when this swipe ends.
/// - minimumDistance: The minimum dragging distance for the gesture to succeed.
func onSwipe(action: @escaping SwipeDirectionAction, minimumDistance: Double = 20) -> some View {
self
.gesture(DragGesture(minimumDistance: minimumDistance, coordinateSpace: .global)
.onEnded { value in
let horizontalAmount = value.translation.width
let verticalAmount = value.translation.height
if abs(horizontalAmount) > abs(verticalAmount) {
horizontalAmount < 0 ? action(.left) : action(.right)
} else {
verticalAmount < 0 ? action(.up) : action(.bottom)
}
})
}
}
enum SwipeDirection {
case up, bottom, left, right
}
使用方法:
var body: some View {
SpriteView(scene: scene)
.ignoresSafeArea()
.onSwipe { direction in
print(direction)
}
}