SwiftUI - Gesture
SwiftUI中的手势是通过modifier来实现了,采用声明式的API相比于UIKit中的实现更易于理解和维护。同时也简化了手势冲突的处理方式,并可通过内建API自定义复杂的手势操作。
本文将从简单的手势开始介绍,包括:
在此基础上,将介绍两个场景:
- gesture修饰符的顺序对效果的影响
- 如何使用simultaneousGesture来组合多个gesture
Tap
为了演示方便,准备了一个简单的View用于接收手势操作:
struct CardView: View {
let selected: Bool
var body: some View {
VStack {
Text("Card")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(self.selected ? Color.purple : Color.orange)
.cornerRadius(30)
}
}
}
在ContentView中使用这个CardView,并添加一个gesture
修饰符,在其中传入一个TapGesture
:
struct ContentView: View {
@State private var selected: Bool = false
var body: some View {
CardView(selected: self.selected)
.gesture(TapGesture(count: 1)
.onEnded {
self.selected.toggle()
}
)
}
}
TapGesture
可以说是所有手势中最简单的一个,它的构造函数中只需要传入一个count,代表点击几下来触发一个事件,若不传入参数,默认值为1。
LongPressGesture
类似于TapGesture,LongPressGesture的触发条件为按住超过一定的时间,这个事件由构造函数的两个参数决定,minimumDuration, maximumDistance。
struct ContentView: View {
@State private var selected: Bool = false
@State private var didLongPress: Bool = false
var body: some View {
CardView(selected: self.selected)
.scaleEffect(didLongPress ? 1.2 : 1)
.animation(.easeInOut)
.frame(width: 200, height: 200, alignment: .center)
.gesture(LongPressGesture(minimumDuration: 1)
.onEnded({_ in self.didLongPress.toggle() })
)
}
使用方式和TapGesture相同,指定一个onEnd的事件即可,这里添加了一个animation的修饰符,目的只是让这个手势产生的效果流畅一些。
LongPressGesture 配合 @GestureState
在上面的例子中,CardView变大之后,若想让它回到原来的大小则需要再次长按。在配合使用@GestureState,可以实现变大后自动回复原状的效果:
struct ContentView: View {
@State private var selected: Bool = false
@GestureState private var didLongPress: Bool = false
var body: some View {
CardView(selected: self.selected)
.scaleEffect(didLongPress ? 1.2 : 1)
.animation(.easeInOut)
.frame(width: 200, height: 200, alignment: .center)
.gesture(LongPressGesture(minimumDuration: 1)
.updating($didLongPress, body: { (value, state, transcation) in
state = value
})
)
}
示例中的update用于返回过程中的状态。在进行下一个手势讲解前,我们看一下当添加了两个手势修饰符的效果:
.gesture(TapGesture().onEnded { print("Tap") })
.gesture(LongPressGesture(minimumDuration: 1).onEnded { _ in print("LongPress") })
如果顺序如上所述,那么在快速点击的情况下,只有Tap生效,在长按的情况下,只有LongPress生效。如果我们翻转两个手势的顺序,情况将大不相同,Tap手势将完全失效。
DragGesture
Drag相对于UIKit中的Pan手势,示例如下所示:
struct ContentView: View {
@State private var selected: Bool = false
@State private var offset: CGSize = CGSize.zero
var body: some View {
CardView(selected: self.selected)
.offset(self.offset)
.gesture(DragGesture()
.onChanged { value in
self.offset = CGSize(width: 0, height: value.translation.height)
}
.onEnded{ value in
self.offset = .zero
}
)
.animation(.spring())
}
}
此处添加了一个Drag手势,通过onChanged获得在drag过程中的状态,onEnded获得手指离开该View后的时间,并且在这两个回调中改变一个@State,无需过多的介绍,声明式的API足以自解释。
Magnification & Rotation Gestures
Magnification相当于UIKit中的Pinch,先看例子:
struct ContentView: View {
@State var magnificationValue: CGFloat = CGFloat(1)
@State var rotationValue: Angle = .zero
var body: some View {
CardView(selected: self.selected)
.frame(width: 200, height: 200, alignment: .center)
.scaleEffect(magnificationValue)
.rotationEffect(rotationValue)
.gesture(MagnificationGesture()
.onChanged({ (value) in
self.magnificationValue = value
})
)
.simultaneousGesture(RotationGesture().onChanged({ (value) in
self.rotationValue = value
}))
}
}
此处MagnificationGesture可配合scaleEffect,开放大缩小当前view,RotationGesture可配合rotationEffect,旋转当前view。同时,这里使用了一个simultaneousGesture,这个修饰符用于组合多个手势共同生效,如示例代码中,两个手势可以共同决定view的大小和角度。到此是不是可以足够体现出声明式API的优势。