基本概念
在ios中我们经常用到的手势操作有点击,长按,拖拽,缩放,旋转。在手势操作时,不同的手势有不同的触发函数,比如,点击在点击完成时触发某个函数,拖拽手势不仅仅在拖拽完成时触发函数,而在拖拽的过程中也可以触发两个函数,在这两个函数中捕捉到不同的参数。对于手势的捕获也有两种方式,我们具体的看下这两种方式
方式一,通过gesture捕获手势
我们可以给View添加一个gesture()的装饰函数来捕捉手势,在这个函数中设定不同Gesture()就可以捕捉到不同的手势。在捕捉到手势之后,还有3个回调函数处理不同的情况,updating(_:body)在手势操作时被调用,这个函数需要绑定一个@GestureState修饰的变量,这个变量在回调时被修改,但只是临时的修改,手势操作完这个变量会被修改为手势操作之前的初始化值.onChange(_:)这个同样是在手势操作时被调用,这个函数中修改的变量在手势操作结束时被保留。onEnd()这个函数时在手势操作成功之后调用。
缩放
我们设定一个图片缩放场景来演示缩放手势,先通过image显示一张图片,我们通过缩放手势让图片放大或者缩小。捕获的gesture是MagnificationGesture.代码如下
@GestureState var zoom:CGFloat=1
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).scaleEffect(zoom).gesture(MagnificationGesture().updating($zoom, body: {
gestureZoom,z,transaction in
z=gestureZoom
}))
设定一个@GestureState修饰的变量zoom,用来控制图片的缩放比例。在这段代码中gestureZoom是我们做缩放手势时的缩放比例,z就是zoom,我们将gestureZoom的数值赋值过去,图片就放大/缩小,当我们手势结束时,zoom就变成1。图片回到原先大小。
@State var zoom:CGFloat=1
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).scaleEffect(zoom).gesture(MagnificationGesture().onChanged({
z in zoom=z
}))
这段代码,我们将zoom用@State修饰,onChange中z是缩放比例,我们将z赋值给zoom。图片同样放大/缩小.但是手势结束时,图片不会还原。
@State var zoom:CGFloat=1
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).scaleEffect(zoom).gesture(MagnificationGesture().onEnded({z in zoom=z}))
这段代码,我们在onEnd中设置zoom的数值,图片只有在手势完成之后才显示出来缩放。
以上的三段代码展示了三个回调函数的作用。那么我们要完成一个图片的缩放功能是如何实现的呢。代码如下
@State var zoom:CGFloat=1
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).scaleEffect(zoom).gesture(MagnificationGesture().onChanged({z in
zoom += z>1 ? 0.05 : -0.05 }))
- 通过
onChange控制图片缩放,手势操作结束之后图片依旧保持缩放状态。 - 没有直接将
z赋值给zoom,而是判断z是放大还是缩小,>1时zoom增加,图片放大,<1时zoom减小,图片缩小。这样做的原因是为了保持图片的连续放大或者缩小。如果我们直接赋值给zoom
,假设上次手势是放大了3倍,而这次手势也是放大操作,但只放大了2倍,那么图片就会由放大3倍变成放大2倍,实际变小了,我们期望是图片继续放大的,这样的结果不是我们期望的。所以,我们根据手势的操作来设置放大比例,手势只要是放大操作,那么放大比例就增加,保证图片是放大的。
拖拽
我们设定场景是,通过拖拽让图片在不同的位置显示。捕获的Gesture是DragGesture.代码如下
@GestureState var dragOffset=CGSize.zero
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).offset(x: dragOffset.width, y: dragOffset.height).gesture(DragGesture().updating($dragOffset, body: { gesture,dragoff,transaction in
dragoff.width=gesture.translation.width
dragoff.height=gesture.translation.height
}))
我们先在updating处理,这个需要绑定的是一个CGSize类型的属性。通过这个属性的width和height来设置图片的偏移位置,在拖拽结束时,图片会到原先的位置。
@State var offsetX:CGFloat=0
@State var offsetY:CGFloat=0
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).offset(x: offsetX, y: offsetY).gesture(DragGesture().onEnded({v in
offsetX=v.translation.width
offsetY=v.translation.height
}))
这段代码是在手势操作结束捕获。图片会移动到手指抬起的位置。手指移动时没有任何变化。
@State var offsetX:CGFloat=0
@State var offsetY:CGFloat=0
let step:CGFloat=3
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).offset(x: offsetX, y: offsetY).gesture(DragGesture().onChanged({gesture in
offsetX+=gesture.translation.width>0 ? step : -step
offsetY+=gesture.translation.height>0 ? step: -step
}))
这段代码,我们用手按住图片进行滑动,图片就随着手移动,实现了图片的拖拽功能。手势结束时图片还是在结束的位置。其中gesture可以认为是一个手势操作的封装对象,我们获取了这个对象中的位移相关的属性。通过判断位移的位置设定图片偏移量x,y的数值。同样为了让图片保持连续移动,我们没有直接将手势结束时的位置赋值给图片,而是判断出方向再将X,Y的偏移量进行增加或者减少。
旋转
通过旋转手势让图片旋转,同样有3个回调函数
@GestureState var gestureAngle:Angle=Angle(degrees: 0)
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(gestureAngle).gesture(RotationGesture().updating($gestureAngle, body: {angle,ganlge,transaction in
ganlge=angle
}))
手势结束后,图片会还原。
@State var angle:Angle=Angle(degrees: 0)
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(RotationGesture().onChanged({ a in
angle=a
}))
手势结束后,图片保留旋转状态
@State var angle:Angle=Angle(degrees: 0)
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(RotationGesture().onEnded({a in angle=a}))
只有在手势结束之后图片才会旋转并保持旋转状态
点击
点击手势只有2个回调函数 updating和onEnd,在gesture时通过count:Int的构造设定需要连续点击的触发次数。
@GestureState var tap:CGFloat=0
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(TapGesture().updating($tap, body: { d,t,trasaction in
print("d \(d)")
print("tap \(t)")
}))
没有任何反应
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(TapGesture(count: 2).onEnded({print("tap over")}))
连续点击2次会打印tap over
长按
长按手势也有三个回调函数
@GestureState var longPress=false
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(LongPressGesture().updating($longPress, body: {b ,state,transaction in
print("b \(b)")
print("state \(state)")
}))
手指放上去就会触发,b就为true。
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(LongPressGesture().onChanged({b in print(b)}))
同updating手指放上去就会触发
Image("j1").resizable().frame(width: 200, height: 200, alignment: .leading).rotationEffect(angle).gesture(LongPressGesture(minimumDuration: 5, maximumDistance: 5).onEnded({b in print(b)}))
onEnd回调函数能体现长按是否被正确触发,在这段代码中构造函数添加了2个参数minimumDuration按住的时间maximumDistance按住时最大的移动距离,按住的时间要大于设定的minimumDuration并且手指移动的距离要小于maximumDistance才会真正触发。如果没有满足上面2个条件,不会打印b。
方式二
SwiftUI对于点击和长按这两个手势提供了几个简单的修饰函数。
点击
对于点击手势有三个函数onTapGesture(perform: <() -> Void),onTapGesture{code},onTapGesture(count: Int, perform: () -> Void)都是在点击手势完成时调用,闭包函数时调用时的代码。具体使用方式可以对比方式一的函数。
长按
长按手势的处理有两个函数onLongPressGesture(perform: () -> Void),onLongPressGesture(minimumDuration: Double, maximumDistance: CGFloat, perform: () -> Void, onPressingChanged: ((Bool) -> Void)?),可以对比方式一的函数写代码。
总结
- 手势捕捉的两种方式。
- 每个手势捕捉之后有3个回调函数
updating(_:body:),onChanged(_:),onEnd(_:)。 - 手势操作的变量修饰:
@GestureState - View的定位函数
offset - 多个手势可以通过
gesture多次捕捉。当然还有simultaneousGesture,highPriorityGesture处理不同情况。
本文详细介绍了SwiftUI中的手势操作,包括点击、长按、拖拽、缩放和旋转。通过两种方式捕获手势,并通过不同回调函数实现各种交互效果。例如,缩放手势通过设置修饰变量控制图片放大缩小;拖拽手势改变图片位置;旋转手势使图片旋转;点击和长按手势则提供简单快捷的处理方式。
3889

被折叠的 条评论
为什么被折叠?



