如何在 SwiftUI 中使用手势
SwiftUI:手势
SwiftUI为我们提供了很多处理视图的手势,并且可以很好地减轻大部分的困难的工作,这样我们就可以专注于重要的部分。很显然,最常见的是我们的朋友ontapGeasure()
,但还有其他几种,还有一些有趣的方法可以将手势组合在一起,值得一试。
我将跳过简单的ontapGeasure()
,因为我们之前已经讨论过很多次了,但是在我们尝试更复杂的事情之前,我想添加一个count
参数,让它们处理两次点击、三次点击等等,比如:
Text("Hello, World!")
.onTapGesture(count: 2) {
print("Double tapped!")
}
好吧,让我们看看比简单的点击更有趣的东西。对于长按操作,您可以使用 onLongPressGeasure()
,如下所示:
Text("Hello, World!")
.onLongPressGesture {
print("Long pressed!")
}
与轻触手势一样,长按手势也可以自定义。例如,您可以指定按下的最短持续时间,因此您的操作闭包仅在经过特定秒数后触发。例如,这只会在两秒钟后触发:
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 2) {
print("Long pressed!")
}
您甚至可以添加第二个闭包,当手势的状态发生变化时触发该闭包。它将被赋予一个布尔参数作为输入,其工作方式如下:
- 一旦您按下,
change
闭包将被调用,其参数设置为 true。 - 如果您在手势被识别之前释放(因此,如果您在使用2秒识别时在1秒后释放),则将调用
change
闭包,并将其参数设置为 false。 - 如果您按住识别的整个长度,那么
change
闭包将被调用,其参数设置为 false(因为手势不再存在),并且您的完成闭包也将被调用。
使用这样的代码亲自尝试:
// Xcode 11.x Swift 5.3以下
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 1, pressing: { inProgress in
print("In progress: \(inProgress)!")
}) {
print("Long pressed!")
}
// Xcode 12.x Swift 5.3 多闭包尾随闭包写法变更
Text("Hello, world!")
.onLongPressGesture(minimumDuration: 1) { inProgress in
print("In progress: \(inProgress)!")
} perform: {
print("Long pressed!")
}
对于更高级的笔势,您应该将gesture()
修饰符与其中一个手势一起使用:DragGesture
、LongPressGesture
、MagnificationGesture
、RotationGesture
和TapGesture
。它们都有特殊的修饰符,通常是onEnded()
和onChanged()
,当手势正在运行(对于onChanged()
)或完成(对于onEnded()
)时,可以使用它们来执行操作。
例如,我们可以在一个视图上附加一个放大的手势,这样就可以放大和缩小视图。这可以通过创建两个@State
属性来存储缩放量,在scaleEffect()
修饰符中使用,然后在手势中设置这些值,如下所示:
struct ContentView: View {
@State private var currentAmount: CGFloat = 0
@State private var finalAmount: CGFloat = 1
var body: some View {
Text("Hello, World!")
.scaleEffect(finalAmount + currentAmount)
.gesture(
MagnificationGesture()
.onChanged { amount in
self.currentAmount = amount - 1
}
.onEnded { amount in
self.finalAmount += self.currentAmount
self.currentAmount = 0
}
)
}
}
对于使用rotationStroke
旋转视图也可以采用完全相同的方法,但我们现在使用的是rotationEffect()
修饰符:
struct ContentView: View {
@State private var currentAmount: Angle = .degrees(0)
@State private var finalAmount: Angle = .degrees(0)
var body: some View {
Text("Hello, World!")
.rotationEffect(currentAmount + finalAmount)
.gesture(
RotationGesture()
.onChanged { angle in
self.currentAmount = angle
}
.onEnded { angle in
self.finalAmount += self.currentAmount
self.currentAmount = .degrees(0)
}
)
}
}
当手势发生冲突时,事情开始变得更有趣——当你有两个或更多的手势可能同时被识别,比如你有一个手势连接到一个视图,而同一个手势附加到它的父视图。
例如,这会将onTapGesture()
添加到文本视图及其父视图:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.onTapGesture {
print("VStack tapped")
}
}
}
在这种情况下,SwiftUI总是优先考虑孩子的手势,这意味着当你点击上面的文本视图时,你会看到“Text tapped”。但是,如果要更改,可以使用highPriorityStrengse()
修饰符来强制触发父对象的手势,如下所示:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.highPriorityGesture(
TapGesture()
.onEnded { _ in
print("VStack tapped")
}
)
}
}
或者,您可以使用simultaneousGesture ()
修饰符告诉SwiftUI您希望父手势和子手势同时触发,如:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.simultaneousGesture(
TapGesture()
.onEnded { _ in
print("VStack tapped")
}
)
}
}
这将打印“Text tapped”和“VStack tapped”。
最后,SwiftUI允许我们创建手势序列,其中一个手势只有在另一个手势首次成功时才会激活。这需要更多的思考,因为手势需要能够相互引用,所以不能直接将它们附加到视图上。
下面是一个演示手势排序的示例,您可以拖动一个圆,但前提是先长按它:
struct ContentView: View {
// 拖动的距离
@State private var offset = CGSize.zero
// 当前是否在拖动
@State private var isDragging = false
var body: some View {
// 一个在移动时更新偏移量和偏移量的 拖动手势
let dragGesture = DragGesture()
.onChanged { value in self.offset = value.translation }
.onEnded { _ in
withAnimation {
self.offset = .zero
self.isDragging = false
}
}
// 一个设置isDragging 的 长按手势
let pressGesture = LongPressGesture()
.onEnded { value in
withAnimation {
self.isDragging = true
}
}
// 强制用户长按然后拖动的组合手势
let combined = pressGesture.sequenced(before: dragGesture)
// 一个64x64的圆,当它被拖动时会放大,将它的偏移量设置为我们从拖动手势返回的值,并使用我们的组合手势
return Circle()
.fill(Color.red)
.frame(width: 64, height: 64)
.scaleEffect(isDragging ? 1.5 : 1)
.offset(offset)
.gesture(combined)
}
}
手势是制作流畅、有趣的用户界面的一个非常好的方法,但是一定要向用户展示他们的工作原理,否则他们会很困惑!