10.手势
10.手势
在SwiftUI中,可以通过手势识别器(Gesture Recognizers)来捕捉和响应用户的手势操作。SwiftUI提供了一些内置的手势识别器,例如拖动手势、轻击手势、长按手势等,同时也支持自定义手势识别器。
内置手势识别器
SwiftUI提供了多种内置手势识别器,可以通过视图修饰符.gesture()来添加手势识别器。常见的内置手势识别器有:
- TapGesture:轻击手势,可以设置点击次数、手指数等属性。
- LongPressGesture:长按手势,可以设置最短持续时间、最大移动距离等属性。
- DragGesture:拖动手势,可以设置最大/最小拖动速度、拖动方向等属性。
- MagnificationGesture:放大/缩小手势,可以设置最小/最大放大比例等属性。
- RotationGesture:旋转手势,可以设置最小/最大旋转角度等属性。
TapGesture
点击手势可以响应用户的单击事件,例如单击一个按钮或图像。使用 .tapGesture() 修饰符来添加这种手势。
Text("Click me!")
.onTapGesture {
print("Text is tapped")
}
在上面的代码中,当用户单击文本视图时,会在控制台上输出 "Text is tapped"。也可以将点击手势应用于任何 SwiftUI 视图,例如图片、按钮、形状等。
LongPressGesture
长按手势可以响应用户长按某个视图的事件。可以使用 .longPressGesture() 修饰符来添加此类手势。
Image(systemName: "trash")
.foregroundColor(.red)
.font(.largeTitle)
.padding()
.background(Color.white)
.cornerRadius(40)
.onLongPressGesture {
print("Delete button is long pressed")
}
在上面的代码中,使用长按手势来响应 "删除" 按钮的长按事件。当用户长按 "删除" 按钮时,将在控制台上输出 "Delete button is long pressed"。
DragGesture
Image(systemName: "arrow.clockwise")
.font(.largeTitle)
.padding()
.background(Color.white)
.cornerRadius(40)
.rotationGesture()
在上面的代码中,使用旋转手势使箭头图标可以旋转。当用户使用两个手指旋转视图时,箭头图标将跟随手势旋转。
MagnificationGesture
缩放手势可以响应用户使用两个手指缩放视图的事件。可以使用 .scaleGesture() 修饰符来添加此类手势。
Image(systemName: "photo")
.resizable()
.scaledToFit()
.padding()
.background(Color.white)
.cornerRadius(40)
.scaleEffect(scale)
.gesture(
MagnificationGesture()
.onChanged { value in
self.scale = value.magnitude
}
)
在上面的代码中,使用缩放手势来响应用户缩放图像视图的事件。当用户使用两个手指缩放视图时,图像视图将按比例缩放。
RotationGesture
可以使用 .dragGesture() 修饰符来添加拖动手势到视图中。这个修饰符可以让用户通过拖动手势来移动视图或者执行其他操作。
struct DragView: View {
@State private var offset = CGSize.zero
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(offset)
.gesture(
DragGesture()
.onChanged { value in
self.offset = value.translation
}
.onEnded { _ in
self.offset = .zero
}
)
}
}
在这个例子中,我们创建了一个 Circle 视图,并使用 .offset() 修饰符将其初始位置移动到了屏幕中心。接着使用 .gesture() 修饰符将 DragGesture 添加到了这个视图中。在 DragGesture 上定义了两个回调函数,一个是 onChanged,用于处理手势拖动过程中的值变化,另一个是 onEnded,用于处理手势结束时的操作。
在 onChanged 中,更新了 offset 变量,将其设置为手势的 translation 值,也就是手指相对于视图初始位置的移动距离。在 onEnded 中,将 offset 重置为 .zero,以将视图移回到初始位置。
自定义手势识别器
在 SwiftUI 中可以通过 Gesture 协议来创建自定义手势。 Gesture 协议是一个类型擦除协议,其具体实现取决于其遵循者。通常可以使用 Gesture 协议中的 onChanged、onEnded、onBegan 等方法来响应手势的不同状态。
通过一个例子来演示如何创建自定义手势识别器。假设我们想要创建一个手势,使得在视图上画一个 "V" 字形时,触发相应的操作。
首先,需要创建一个实现 Gesture 协议的结构体。在这个结构体中,我们需要实现 init 和 body 两个方法。
struct VGesture: Gesture {
let minimumDistance: CGFloat = 50
var body: some Gesture {
DragGesture(minimumDistance: 0)
.onChanged { value in
let startPoint = value.startLocation
let endPoint = value.location
let distance = endPoint.distance(to: startPoint)
guard distance >= minimumDistance else { return }
let angle = endPoint.angle(to: startPoint)
if angle > -45 && angle < 45 {
print("V gesture detected!")
}
}
}
init() { }
}
在这个例子中创建了一个名为 VGesture 的结构体,并实现了 body 方法。使用了 DragGesture,并通过 minimumDistance 属性指定了拖动手势的最小距离。在 onChanged 方法中,我们检查了手势的起点和终点之间的距离是否足够长,然后计算了这两点之间的角度,并在特定的角度范围内触发了相应的操作。
在视图中使用自定义手势识别器:
struct ContentView: View {
var body: some View {
Text("Hello, World!")
.gesture(VGesture())
}
}
将 VGesture 添加到了 Text 视图的手势中。在视图上画出一个 "V" 字形时,就会触发相应的操作。
simultaneousGesture
使用 simultaneousGesture 可以让视图同时响应多个手势,例如同时响应拖动和缩放手势:
@State private var scale: CGFloat = 1.0
@State private var offset = CGSize.zero
var body: some View {
Image(systemName: "star.circle")
.resizable()
.frame(width: 200, height: 200)
.foregroundColor(.orange)
.scaleEffect(scale)
.offset(offset)
.gesture(
MagnificationGesture()
.onChanged { value in
self.scale = value.magnitude
}
.simultaneously(with: DragGesture()
.onChanged { value in
self.offset = value.translation
}
)
)
}
使用 simultaneous(with:) 将缩放手势和拖动手势同时绑定在图像视图上。
sequenced
使用 sequenced(before:) 可以让手势按顺序执行,例如先拖动,再缩放:
@State private var scale: CGFloat = 1.0
@State private var offset = CGSize.zero
var body: some View {
Image(systemName: "star.circle")
.resizable()
.frame(width: 200, height: 200)
.foregroundColor(.orange)
.scaleEffect(scale)
.offset(offset)
.gesture(
DragGesture()
.onChanged { value in
self.offset = value.translation
}
.sequenced(before: MagnificationGesture()
.onChanged { value in
self.scale = value.magnitude
}
)
)
}
使用 sequenced(before:) 将缩放手势放在拖动手势之后执行。
exclusiveGesture
使用 exclusiveGesture 可以阻止其他手势的响应,例如只响应双指拖动手势:
@State private var offset = CGSize.zero
var body: some View {
Image(systemName: "star.circle")
.resizable()
.frame(width: 200, height: 200)
.foregroundColor(.orange)
.offset(offset)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
self.offset = value.translation
}
.exclusively(before: DragGesture(minimumDistance: 50)
.onChanged { value in
print("Double finger drag")
}
)
)
}
使用 exclusively(before:) 将双指拖动手势设置为独占手势,其他手势将被阻止响应。
交互动画
在 SwiftUI 中可以利用手势和动画来实现丰富的交互效果
点击缩放
可以使用 .scaleEffect 修饰符来实现点击时的缩放效果。具体实现方式是给 View 添加一个 .scaleEffect 修饰符,并在手势响应时将其设置为 0.8,释放时恢复为 1.0。
struct ScaleButton: View {
@State private var scale: CGFloat = 1.0
var body: some View {
Text("Click Me")
.scaleEffect(scale)
.padding()
.onTapGesture {
withAnimation(.spring()) {
scale = 0.8
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation(.spring()) {
scale = 1.0
}
}
}
}
}
在这个示例中使用了 .spring() 来定义动画类型,并在两次动画之间添加了一个延迟以确保效果正常。
拖拽移动
可以使用 .offset 修饰符来实现拖拽移动的效果。具体实现方式是给 View 添加一个 .offset 修饰符,并在手势响应时更新其值。
struct DragView: View {
@GestureState private var dragState = DragState.inactive
@State private var position = CGSize.zero
var body: some View {
let dragGesture = DragGesture()
.updating($dragState) { value, dragInfo, _ in
dragInfo = .dragging(translation: value.translation)
}
.onEnded { value in
position.height += value.translation.height
position.width += value.translation.width
}
return Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.scaleEffect(dragState.isActive ? 1.2 : 1.0)
.offset(
x: position.width + dragState.translation.width,
y: position.height + dragState.translation.height
)
.gesture(dragGesture)
}
}
enum DragState {
case inactive
case dragging(translation: CGSize)
}
使用了 @GestureState 来定义手势状态,并使用了 DragGesture 来处理拖拽事件。使用 updating 方法来更新手势状态,并在 onEnded 方法中更新位置信息。