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优秀交互必不可少的!

相关推荐
I烟雨云渊T4 小时前
iOS 门店营收表格功能的实现
ios
明月看潮生10 小时前
青少年编程与数学 01-011 系统软件简介 07 iOS操作系统
ios·青少年编程·操作系统·系统软件
90后的晨仔12 小时前
RxSwift 框架解析
前端·ios
大熊猫侯佩16 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)
swiftui·swift·apple watch
大熊猫侯佩16 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(四)
数据库·swiftui·apple watch
可爱小仙子16 小时前
ios苹果系统,js 滑动屏幕、锚定无效
前端·javascript·ios
未来猫咪花17 小时前
# Flutter状态管理对比:view_model vs Riverpod
flutter·ios·android studio
咕噜企业签名分发-淼淼20 小时前
开发源码搭建一码双端应用分发平台教程:逐步分析注意事项
android·ios
键盘敲没电1 天前
【IOS】GCD学习
学习·ios·objective-c·xcode
SY.ZHOU1 天前
Significant Location Change
macos·ios·cocoa