如何在 SwiftUI 中使用手势

SwiftUI:手势

SwiftUI为我们提供了很多处理视图的手势,并且可以很好地减轻大部分的困难的工作,这样我们就可以专注于重要的部分。很显然,最常见的是我们的朋友ontapGeasure(),但还有其他几种,还有一些有趣的方法可以将手势组合在一起,值得一试。

我将跳过简单的ontapGeasure(),因为我们之前已经讨论过很多次了,但是在我们尝试更复杂的事情之前,我想添加一个count参数,让它们处理两次点击、三次点击等等,比如:

Text("Hello, World!")
    .onTapGesture(count: 2) {
        print("Double tapped!")
    }

好吧,让我们看看比简单的点击更有趣的东西。对于长按操作,您可以使用 onLongPressGeasure(),如下所示:

Text("Hello, World!")
    .onLongPressGesture {
        print("Long pressed!")
    }

与轻触手势一样,长按手势也可以自定义。例如,您可以指定按下的最短持续时间,因此您的操作闭包仅在经过特定秒数后触发。例如,这只会在两秒钟后触发:

Text("Hello, World!")
    .onLongPressGesture(minimumDuration: 2) {
        print("Long pressed!")
    }

您甚至可以添加第二个闭包,当手势的状态发生变化时触发该闭包。它将被赋予一个布尔参数作为输入,其工作方式如下:

  1. 一旦您按下,change闭包将被调用,其参数设置为 true。
  2. 如果您在手势被识别之前释放(因此,如果您在使用2秒识别时在1秒后释放),则将调用change闭包,并将其参数设置为 false。
  3. 如果您按住识别的整个长度,那么change闭包将被调用,其参数设置为 false(因为手势不再存在),并且您的完成闭包也将被调用。

使用这样的代码亲自尝试:

// Xcode 11.x Swift 5.3以下
Text("Hello, World!")
    .onLongPressGesture(minimumDuration: 1, pressing: { inProgress in
        print("In progress: \(inProgress)!")
    }) {
        print("Long pressed!")
    }

// Xcode 12.x Swift 5.3 多闭包尾随闭包写法变更
Text("Hello, world!")
    .onLongPressGesture(minimumDuration: 1) { inProgress in
        print("In progress: \(inProgress)!")
    } perform: {
        print("Long pressed!")
    }

对于更高级的笔势,您应该将gesture()修饰符与其中一个手势一起使用:DragGestureLongPressGestureMagnificationGestureRotationGestureTapGesture。它们都有特殊的修饰符,通常是onEnded()onChanged(),当手势正在运行(对于onChanged())或完成(对于onEnded())时,可以使用它们来执行操作。

例如,我们可以在一个视图上附加一个放大的手势,这样就可以放大和缩小视图。这可以通过创建两个@State属性来存储缩放量,在scaleEffect()修饰符中使用,然后在手势中设置这些值,如下所示:

struct ContentView: View {
    @State private var currentAmount: CGFloat = 0
    @State private var finalAmount: CGFloat = 1

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

对于使用rotationStroke旋转视图也可以采用完全相同的方法,但我们现在使用的是rotationEffect()修饰符:

struct ContentView: View {
    @State private var currentAmount: Angle = .degrees(0)
    @State private var finalAmount: Angle = .degrees(0)

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

当手势发生冲突时,事情开始变得更有趣——当你有两个或更多的手势可能同时被识别,比如你有一个手势连接到一个视图,而同一个手势附加到它的父视图。

例如,这会将onTapGesture()添加到文本视图及其父视图:

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

在这种情况下,SwiftUI总是优先考虑孩子的手势,这意味着当你点击上面的文本视图时,你会看到“Text tapped”。但是,如果要更改,可以使用highPriorityStrengse()修饰符来强制触发父对象的手势,如下所示:

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

或者,您可以使用simultaneousGesture ()修饰符告诉SwiftUI您希望父手势和子手势同时触发,如:

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

这将打印“Text tapped”和“VStack tapped”。

最后,SwiftUI允许我们创建手势序列,其中一个手势只有在另一个手势首次成功时才会激活。这需要更多的思考,因为手势需要能够相互引用,所以不能直接将它们附加到视图上。

下面是一个演示手势排序的示例,您可以拖动一个圆,但前提是先长按它:

struct ContentView: View {
    // 拖动的距离
    @State private var offset = CGSize.zero

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

    var body: some View {
        // 一个在移动时更新偏移量和偏移量的 拖动手势
        let dragGesture = DragGesture()
            .onChanged { value in self.offset = value.translation }
            .onEnded { _ in
                withAnimation {
                    self.offset = .zero
                    self.isDragging = false
                }
            }

        // 一个设置isDragging 的 长按手势
        let pressGesture = LongPressGesture()
            .onEnded { value in
                withAnimation {
                    self.isDragging = true
                }
            }

        // 强制用户长按然后拖动的组合手势
        let combined = pressGesture.sequenced(before: dragGesture)

        // 一个64x64的圆,当它被拖动时会放大,将它的偏移量设置为我们从拖动手势返回的值,并使用我们的组合手势
        return Circle()
            .fill(Color.red)
            .frame(width: 64, height: 64)
            .scaleEffect(isDragging ? 1.5 : 1)
            .offset(offset)
            .gesture(combined)
    }
}

手势是制作流畅、有趣的用户界面的一个非常好的方法,但是一定要向用户展示他们的工作原理,否则他们会很困惑!

译自 How to use gestures in SwiftUI

你可能感兴趣的:(如何在 SwiftUI 中使用手势)