
苹果公司做对了很重要的一点:移动设备要为手指设计(你看,我是个很客观的人,不总是说苹果的坏话)。
相比而言,windows的移动化的一大失败就是手指很难精确点中狭小的菜单,而通过长按呼出右键菜单也完全失去了"快捷"的意义。
目录
[.onTapGesture 点击](#.onTapGesture 点击)
[.onLongPressGesture 长按](#.onLongPressGesture 长按)
[DragGesture 拖动 任意移动对象](#DragGesture 拖动 任意移动对象)
[MagnificationGesture 缩放](#MagnificationGesture 缩放)
[RotationGesture 旋转](#RotationGesture 旋转)
.onTapGesture 点击
点击很容易理解,就是鼠标单击。点击和Button有什么区别?Button按下有默认效果处理,点击就是点击,没有效果的。
点击可以用.onTapGesture修饰,最简单。也可以用比较复杂的.gesture,.gesture里面可以综合综合多种手势。
因为太简单,我们代码后面再列。
.onLongPressGesture 长按
长按可以理解为对应鼠标右键,长按有参数控制最短时间和手指移动最大距离,不过一般来讲不需要修改,遵循系统就可以了。长按可以区分按下和结束,按下会被不断触发,可以搞点动画,结束处理和单击差不多。按下和结束通过bool参数来区分。
下面是点击和长按的代码:
Swift
@State var str = "info"
var body: some View {
VStack {
Spacer()
Text(str)
.padding()
.border(.blue)
.onTapGesture {
str = "Tap Text"
}
.onLongPressGesture(pressing: { b in
if b { str = "...... Text" }
}) {
str = "LongPress Text"
}
Spacer()
.background(.green) //没用
.border(.green) //没用
.onTapGesture { //没用,Spacer啥都不支持
str = "Tap Spacer"
}
}
.background(.green) //背景区域都有效
.border(.green, width: 5) //仅对边框有效,但是当边框位置改变时,原来的位置仍然有效,或许是BUG
.onTapGesture {
str = "Tap VStack"
}
}
代码里对不同视图都做了点击处理。实测Spacer是完全不响应的,设置边界和背景也没有意义,也就是Spacer只是用来改变布局的。对VStack,设置背景的话整个背景都有效,设置边界则只有边界上才有效,而且,当边界改变时,新边界有效,原来的边界位置也仍然有效,不晓得是feature还是BUG。
程序很简单,都是改变文本内容,初始:

点击:



长按过程中截不到图,效果是有的。
DragGesture 拖动 任意移动对象
注意是拖动,不是拖放,onDrag是拖放,没有onDragGesture,只能用.gesture(DragGesture())来实现。
拖动最简单的应用就是改变对象位置,然而可笑的是,书上和网上的例子试了都有问题,手指放开就会回到原位,下面的代码是我重新琢磨的:
Swift
@State var offset = CGSize.zero
@State var lastoffset = CGSize.zero
//in body
Text(str)//长按下面增加两个修饰
.offset(offset)
.gesture(
DragGesture()
.onChanged { Value in
offset.width =
lastoffset.width + Value.translation.width
offset.height =
lastoffset.height + Value.translation.height
}
.onEnded { Value in
lastoffset.width += Value.translation.width
lastoffset.height += Value.translation.height
}
)
.offset用来指示位置移动,参数是移动多少,就是说每次绑定变量offset改变时触发.offset修饰,基于原始位置移动对象。DragGesture的onChanged和onEnded参数都是DragGesture.Value,包含了手势的相关参数,包括起始位置、当前位置、当前偏移量预测结束位置、预测结束偏移量和持续时间。由于.offset的参数是偏移量,因此我们用了translation。但是每次拖动时translation都是基于本次拖动,所以需要额外一个变量来记住上次拖动最后停留在哪里了。
代码中lastoffset用来记录本次拖动之前的偏移量,offset则是本次之前的偏移量加上当前拖动的偏移量。
效果,原始位置:

拖到左上角:

再拖到右下角:

我们注意到文本变成了省略号,这是在长按过程中设置的。刚开始动作被识别为长按,然后变成了拖动,因此最后长按结束动作不会被触发。
MagnificationGesture 缩放
缩放和拖动一样,没有独立的onMagnificationGesture,只能在.gesture处理。.gesture如果需要同时处理多个手势,需要使用SimultaneousGesture。如果希望只识别优先级高的,则使用ExclusiveGesture。
下面的代码同时支持拖动和缩放:
Swift
@State var scale: CGFloat = 1.0
.scaleEffect(scale)
.gesture(
SimultaneousGesture( //ExclusiveGesture
DragGesture()
.onChanged { Value in
offset.width =
lastoffset.width + Value.translation.width
offset.height =
lastoffset.height + Value.translation.height
}
.onEnded { Value in
lastoffset.width += Value.translation.width
lastoffset.height += Value.translation.height
},
MagnificationGesture()
.onChanged { value in
scale = value
}
)
)
这段代码在预览里不太容易做出缩放效果,需要借助option键,而且由于Text视图比较小,容易因为系统认为的两指位置超出范围而无效,但在实体机上是比较容易的。由于同时支持了拖动,缩放完位置也会变。
.scaleEffect控制视图的缩放效果,参数是缩放比例,onChanged的参数也是缩放比例,所以只要把参数复制给绑定变量就行了。
效果:


与拖动一样,也有onEnded可供处理。
RotationGesture 旋转
旋转是两指旋转(而不是单指)。代码也完全类似缩放,.rotationEffect修饰视图的旋转,参数是角度,类型为Angle。角度分角度和弧度两种,不过只影响Angle类型的使用,我们这里直接从onChanged参数复制,不涉及具体计算。
由于ExclusiveGesture只支持两种手势组合,所以需要用.highPriorityGesture代替.gesture,这两个的区别顾名思义,一个是高优先级,一个是默认优先级。
代码:
Swift
@State private var angle: Angle = Angle(degrees: 90) //.zero // 旋转角度
.rotationEffect(angle)
.gesture(
。。。。。。
)
.highPriorityGesture(
RotationGesture()
.onChanged { value in
angle = value
}
)

手势组合
前面我们已经使用了不同的手势组合方法,实话说有点乱。
下面是本篇的完整代码:
Swift
import SwiftUI
struct SwiftUIViewGesture: View {
@State var str = "info" //跟踪信息
@State var offset = CGSize.zero //偏移量,基于原始位置
@State var lastoffset = CGSize.zero //拖动时的基础偏移量
@State var scale: CGFloat = 1.0 //放大倍数
@State private var angle: Angle = Angle(degrees: 90) //.zero // 旋转角度
var body: some View {
VStack {
Spacer()
Text(str)
.padding()
.border(.blue)
.onTapGesture {
str = "Tap Text"
}
.onLongPressGesture(pressing: { b in
if b { str = "...... Text" }
}) {
str = "LongPress Text"
}
.offset(offset)
.scaleEffect(scale)
.rotationEffect(angle)
.gesture(
SimultaneousGesture( //仅处理优先级高的
//ExclusiveGesture(//同时处理
DragGesture()
.onChanged { Value in
offset.width =
lastoffset.width + Value.translation.width
offset.height =
lastoffset.height + Value.translation.height
}
.onEnded { Value in
lastoffset.width += Value.translation.width
lastoffset.height += Value.translation.height
},
MagnificationGesture()
.onChanged { value in
scale = value
}
)
)
.highPriorityGesture(
RotationGesture()
.onChanged { value in
angle = value
}
)
Spacer()
.background(.green) //没用
.border(.green) //没用
.onTapGesture { //没用,Spacer啥都不支持
str = "Tap Spacer"
}
}
.background(.green) //背景区域都有效
.border(.green, width: 5) //仅对边框有效,但是当边框位置改变时,原来的位置仍然有效,或许是BUG
.onTapGesture {
str = "Tap VStack"
}
}
}
#Preview {
SwiftUIViewGesture()
}