iOS开发 SwiftUI 15:手势 拖动 缩放 旋转

苹果公司做对了很重要的一点:移动设备要为手指设计(你看,我是个很客观的人,不总是说苹果的坏话)。

相比而言,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()
}
相关推荐
ujainu4 小时前
Flutter + OpenHarmony 游戏开发进阶:虚拟摄像机系统——平滑跟随与坐标偏移
开发语言·flutter·游戏·swift·openharmony
森之鸟5 小时前
iOS云打包之Shorebird
ios
GuokLiu5 小时前
260203-OpenWebUI-在Windows上和RHEL上部署Caddy的步骤+在iPhone上操作的步骤
windows·ios·iphone
2501_9159214321 小时前
傻瓜式 HTTPS 抓包,简单抓取iOS设备数据
android·网络协议·ios·小程序·https·uni-app·iphone
恋猫de小郭1 天前
Flutter 在 Android 出现随机字体裁剪?其实是图层合并时的边界计算问题
android·flutter·ios
2501_915918411 天前
把 iOS 性能监控融入日常开发与测试流程的做法
android·ios·小程序·https·uni-app·iphone·webview
Digitally1 天前
如何轻松地将大型音频文件从 iPhone 发送到不同的设备
ios·iphone
catchadmin1 天前
PHP 现在可以零成本构建原生 iOS 和 Android 应用 NativePHP for Mobile v3 发布
android·ios·php
TheNextByte11 天前
如何将照片从 iPhone 传输到三星?
ios·iphone