SwiftUI如何处理手势

SwiftUI处理手势的API非常丰富,而且把反锁的部分都做了封装少了很多麻烦。最常用的就是onTapGesture(),当然还有其他的API,也可以把这些API组合使用。

首先看看最简单的onTapGesture()

官方定义:

swift 复制代码
func onTapGesture(
  count: Int = 1,
  perform action: @escaping () -> Void
) -> some View

参数count是说一共摸或者点击多少次才可以触发第二个参数的动作。默认是一次。

swift 复制代码
Text("Hello, World!")
    .onTapGesture(count: 2) {
        print("Double tapped!")
    }

这里count明确指定了需要两次才会打印Double tapped!

处理长按可以使用onLongPressGesture(), 例如:

swift 复制代码
Text("Hello, World!")
    .onLongPressGesture {
        print("Long pressed!")
    }

和处理tap手势一样,长按手势也可以定制处理。比如,可以指定最少时长:

swift 复制代码
Text("Hello, World!")
    .onLongPressGesture(minimumDuration: 2) {
        print("Long pressed!")
    }

还可以指定另外一个闭包来处理长按手势状态的变化:

scss 复制代码
Text("Hello, World!")
    .onLongPressGesture(minimumDuration: 1) {
        print("Long pressed!")
    } onPressingChanged: { inProgress in
        print("In progress: (inProgress)!")
    }

可以看到第二个闭包接收了一个bool型的参数,它可以这样处理:

  1. 在用户按下的时候就会调用这个闭包,参数的值为true
  2. 如果在手势被确认为长按(比如在设定长按最少需要两秒,而在一秒钟的时候就释放)之前就松开手指,第二个闭包也会执行,参数值为false
  3. 如果手势被确认(按的时间足够长),第二个闭包会被执行并且参数值为false。因为这个时候会执行第一个闭包。

对于更加复杂的手势可以使用gesture()方法和一些手势结构体结合来处理。手势结构体有:DragGesture, LongPressGesture, MagnificationGesture, RotationGestureTapGesture。当然少不了用到这些结构体的方法: onEnd()onChanged()。手势进行中使用onChanged(),手势完成用onEnd()

下面的例子里,给一个视图加上一个磁性手势。通过手势来缩放该视图。这不仅需要手势,还需要一个@State属性来存储缩放大小值,然后在scaleEffect()里使用就可以达到想要的效果。例如:

swift 复制代码
struct ContentView: View {
    @State private var currentAmount = 0.0
    @State private var finalAmount = 1.0

    var body: some View {
        Text("Hello, World!")
            .scaleEffect(finalAmount + currentAmount)
            .gesture(
                MagnificationGesture()
                    .onChanged { amount in
                        currentAmount = amount - 1
                    }
                    .onEnded { amount in
                        finalAmount += currentAmount 
                        currentAmount = 0
                    }
            )
    }
}

同样的道理,可以使用RotationGesture来实现,这是这次要调用rotationEffect()。例如:

swift 复制代码
struct ContentView: View {
    @State private var currentAmount = Angle.zero
    @State private var finalAmount = Angle.zero

    var body: some View {
        Text("Hello, World!")
            .rotationEffect(currentAmount + finalAmount)
            .gesture(
                RotationGesture()
                    .onChanged { angle in
                        currentAmount = angle
                    }
                    .onEnded { angle in
                        finalAmount += currentAmount
                        currentAmount = .zero
                    }
            )
    }
}

还有一种很有趣的情况。如果一个试图挂了一个手势识别方法,而它的父试图也挂了一个会发生什么呢?比如一个文本试图和它的父试图都挂了onTapGesture()。例如:

swift 复制代码
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .onTapGesture {
                    print("Text tapped")
                }
        }
        .onTapGesture {
            print("VStack tapped")
        }
    }
}

在这种情况下,SwiftUI会把优先权交给子视图 。也就是说,点了TextView会显示Text Tapped 。如果要强行改变这个事实可以使用highPriorityGesture()。例如:

swift 复制代码
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .onTapGesture {
                    print("Text tapped")
                }
        }
        .highPriorityGesture(
            TapGesture()
                .onEnded { _ in
                    print("VStack tapped")
                }
        )
    }
}

于是就不得不提另外一个具有类似功能的方法了simultaneousGesture()。它会告诉SwiftUI同时满足两个试图的手势功能。例如:

swift 复制代码
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .onTapGesture {
                    print("Text tapped")
                }
        }
        .simultaneousGesture(
            TapGesture()
                .onEnded { _ in
                    print("VStack tapped")
                }
        )
    }
}

这是会同时显示Text tappedVStack tapped

最后,SwiftUI还支持使用手势序列。就是把几个手势组合到一起。要使用这个功能就不能像上面的例子一样简单的挂在某个试图上了。

这个例子是长按之后拖动一个圆形,看看下面的代码:

swift 复制代码
struct ContentView: View {
    // 圆形被拖动了多远
    @State private var offset = CGSize.zero

    // 当前是否被拖动
    @State private var isDragging = false

    var body: some View {
        // 拖动手势,拖动时更新offset和isDragging属性
        let dragGesture = DragGesture()
            .onChanged { value in offset = value.translation }
            .onEnded { _ in
                withAnimation {
                    offset = .zero
                    isDragging = false
                }
            }

        // 一个长按手势,完成之后可以开启拖动
        let pressGesture = LongPressGesture()
            .onEnded { value in
                withAnimation {
                    isDragging = true
                }
            }

        // 手势的组合,组合了长按和拖动手势
        let combined = pressGesture.sequenced(before: dragGesture)

        // 一个64*64的圆形。拖动时变大,拖动时把拖动的值作为偏移值设置给圆形。最受用gesture方法设置了上面的组合手势
        Circle()
            .fill(.red)
            .frame(width: 64, height: 64)
            .scaleEffect(isDragging ? 1.5 : 1)
            .offset(offset)
            .gesture(combined)
    }
}

手势处理,是实现app优秀交互必不可少的!

相关推荐
QuantumLeap丶4 小时前
《Flutter全栈开发实战指南:从零到高级》- 09 -常用UI组件库实战
flutter·ios·dart
2501_915918415 小时前
App 上架苹果商店全流程详解 从开发者账号申请到开心上架(Appuploader)跨平台免 Mac 上传实战指南
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
2501_916007477 小时前
从零开始学习iOS App开发:Xcode、Swift和发布到App Store完整教程
android·学习·ios·小程序·uni-app·iphone·xcode
Pluto53810 小时前
第一个app产品的迭代
ios·github
2501_9159214311 小时前
iOS 26 CPU 使用率监控策略 多工具协同构建性能探索体系
android·ios·小程序·https·uni-app·iphone·webview
狂团商城小师妹11 小时前
JAVA国际版同城打车源码同城服务线下结账系统源码适配PAD支持Android+IOS+H5
android·java·ios·小程序·交友
游戏开发爱好者812 小时前
iOS 应用逆向对抗手段,多工具组合实战(iOS 逆向防护/IPA 混淆/无源码加固/Ipa Guard CLI 实操)
android·ios·小程序·https·uni-app·iphone·webview
东坡肘子12 小时前
惊险但幸运,两次!| 肘子的 Swift 周报 #0109
人工智能·swiftui·swift
00后程序员张12 小时前
iOS 文件管理与导出实战,多工具协同打造高效数据访问与调试体系
android·macos·ios·小程序·uni-app·cocoa·iphone
AirDroid_cn19 小时前
在 iOS 18 中,如何将锁屏底部的 “手电筒” 替换为其他应用?
ios