SwiftUI处理手势的API非常丰富,而且把反锁的部分都做了封装少了很多麻烦。最常用的就是onTapGesture()
,当然还有其他的API,也可以把这些API组合使用。
首先看看最简单的onTapGesture()
。
官方定义:
swift
func onTapGesture(
count: Int = 1,
perform action: @escaping () -> Void
) -> some View
参数count
是说一共摸或者点击多少次才可以触发第二个参数的动作。默认是一次。
swift
Text("Hello, World!")
.onTapGesture(count: 2) {
print("Double tapped!")
}
这里count
明确指定了需要两次才会打印Double tapped!。
处理长按可以使用onLongPressGesture()
, 例如:
swift
Text("Hello, World!")
.onLongPressGesture {
print("Long pressed!")
}
和处理tap手势一样,长按手势也可以定制处理。比如,可以指定最少时长:
swift
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 2) {
print("Long pressed!")
}
还可以指定另外一个闭包来处理长按手势状态的变化:
scss
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 1) {
print("Long pressed!")
} onPressingChanged: { inProgress in
print("In progress: (inProgress)!")
}
可以看到第二个闭包接收了一个bool型的参数,它可以这样处理:
- 在用户按下的时候就会调用这个闭包,参数的值为
true
。 - 如果在手势被确认为长按(比如在设定长按最少需要两秒,而在一秒钟的时候就释放)之前就松开手指,第二个闭包也会执行,参数值为
false
。 - 如果手势被确认(按的时间足够长),第二个闭包会被执行并且参数值为
false
。因为这个时候会执行第一个闭包。
对于更加复杂的手势可以使用gesture()
方法和一些手势结构体结合来处理。手势结构体有:DragGesture
, LongPressGesture
, MagnificationGesture
, RotationGesture
和TapGesture
。当然少不了用到这些结构体的方法: onEnd()
和onChanged()
。手势进行中使用onChanged()
,手势完成用onEnd()
。
下面的例子里,给一个视图加上一个磁性手势。通过手势来缩放该视图。这不仅需要手势,还需要一个@State
属性来存储缩放大小值,然后在scaleEffect()
里使用就可以达到想要的效果。例如:
swift
struct ContentView: View {
@State private var currentAmount = 0.0
@State private var finalAmount = 1.0
var body: some View {
Text("Hello, World!")
.scaleEffect(finalAmount + currentAmount)
.gesture(
MagnificationGesture()
.onChanged { amount in
currentAmount = amount - 1
}
.onEnded { amount in
finalAmount += currentAmount
currentAmount = 0
}
)
}
}
同样的道理,可以使用RotationGesture
来实现,这是这次要调用rotationEffect()
。例如:
swift
struct ContentView: View {
@State private var currentAmount = Angle.zero
@State private var finalAmount = Angle.zero
var body: some View {
Text("Hello, World!")
.rotationEffect(currentAmount + finalAmount)
.gesture(
RotationGesture()
.onChanged { angle in
currentAmount = angle
}
.onEnded { angle in
finalAmount += currentAmount
currentAmount = .zero
}
)
}
}
还有一种很有趣的情况。如果一个试图挂了一个手势识别方法,而它的父试图也挂了一个会发生什么呢?比如一个文本试图和它的父试图都挂了onTapGesture()
。例如:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.onTapGesture {
print("VStack tapped")
}
}
}
在这种情况下,SwiftUI会把优先权交给子视图 。也就是说,点了TextView
会显示Text Tapped 。如果要强行改变这个事实可以使用highPriorityGesture()
。例如:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.highPriorityGesture(
TapGesture()
.onEnded { _ in
print("VStack tapped")
}
)
}
}
于是就不得不提另外一个具有类似功能的方法了simultaneousGesture()
。它会告诉SwiftUI同时满足两个试图的手势功能。例如:
swift
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还支持使用手势序列。就是把几个手势组合到一起。要使用这个功能就不能像上面的例子一样简单的挂在某个试图上了。
这个例子是长按之后拖动一个圆形,看看下面的代码:
swift
struct ContentView: View {
// 圆形被拖动了多远
@State private var offset = CGSize.zero
// 当前是否被拖动
@State private var isDragging = false
var body: some View {
// 拖动手势,拖动时更新offset和isDragging属性
let dragGesture = DragGesture()
.onChanged { value in offset = value.translation }
.onEnded { _ in
withAnimation {
offset = .zero
isDragging = false
}
}
// 一个长按手势,完成之后可以开启拖动
let pressGesture = LongPressGesture()
.onEnded { value in
withAnimation {
isDragging = true
}
}
// 手势的组合,组合了长按和拖动手势
let combined = pressGesture.sequenced(before: dragGesture)
// 一个64*64的圆形。拖动时变大,拖动时把拖动的值作为偏移值设置给圆形。最受用gesture方法设置了上面的组合手势
Circle()
.fill(.red)
.frame(width: 64, height: 64)
.scaleEffect(isDragging ? 1.5 : 1)
.offset(offset)
.gesture(combined)
}
}
手势处理,是实现app优秀交互必不可少的!