SwiftUI:如何处理轻按和长按按钮?

Ger*_*ard 25 button long-press swiftui

我在 SwiftUI 中有一个按钮,我希望能够对“点击按钮”(正常点击/点击)和“长按”执行不同的操作。

这在 SwiftUI 中可能吗?

这是我现在拥有的按钮的简单代码(仅处理“正常”点击/触摸案例)。

Button(action: {self.BLEinfo.startScan() }) {
                        Text("Scan")
                    } .disabled(self.BLEinfo.isScanning)
Run Code Online (Sandbox Code Playgroud)

我已经尝试添加一个“长按手势”,但它仍然只“执行”“正常/短”点击。这是我试过的代码:

Button(action: {self.BLEinfo.startScan() }) {
                        Text("Scan")
                            .fontWeight(.regular)
                            .font(.body)
                        .gesture(
                            LongPressGesture(minimumDuration: 2)
                                .onEnded { _ in
                                    print("Pressed!")
                            }
                        )
                    }
Run Code Online (Sandbox Code Playgroud)

谢谢!

杰拉德

nor*_*hov 33

我尝试了很多东西,但最后我做了这样的事情:

    Button(action: {
    }) {
        VStack {
            Image(self.imageName)
                .resizable()
                .onTapGesture {
                    self.action(false)
                }
                .onLongPressGesture(minimumDuration: 0.1) {
                    self.action(true)
                }
        }
    }
Run Code Online (Sandbox Code Playgroud)

它仍然是一个有效果的按钮,但短按和长按不同。

  • 请注意,从 Xcode 11.2.1 / iOS 13.2 开始,这里的顺序似乎很重要。在 `onTapGesture()` 之前使用 `onLongPressGesture()` 将忽略后者。 (10认同)
  • 这没有点击或按下的动画并阻止“action”中的代码 (2认同)

And*_*gel 9

我刚刚发现效果取决于实现的顺序。按照以下顺序实现手势检测,似乎可以检测和识别所有三个手势:

  1. 处理双击手势
  2. 处理 longPressGesture
  3. 处理单击手势

在 Xcode 版本 11.3.1 (11C504) 上测试

    fileprivate func myView(_ height: CGFloat, _ width: CGFloat) -> some View {
    return self.textLabel(height: height, width: width)
        .frame(width: width, height: height)
        .onTapGesture(count: 2) {
            self.action(2)
        }
        .onLongPressGesture {
            self.action(3)
        }
        .onTapGesture(count: 1) {
            self.action(1)
        }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

(后来,2023年\xe2\x80\xa6)

\n

我尝试了所有能找到的类似解决方案,但每个解决方案都会产生某种冲突,其中一个手势阻止了另一个手势,或类似的情况。

\n

PrimitiveButtonStyle在阅读文档后,我决定自己实现一个符合以下标准的类型:

\n

Specify a style that conforms to PrimitiveButtonStyle to create a button with custom interaction behavior.

\n

本质上,该样式使用onTapGestureonLongPressGesture,但所有“点击手势”所做的只是使用协议的公开configuration变量来激活按钮的“操作”(如声明时定义的那样)。

\n

作为奖励,长按手势的pressed变量可用于决定是否显示指示按钮被按下的自定义反馈,无论哪种手势最终成功。

\n
// Conform to `PrimitiveButtonStyle` for custom interaction behaviour\n\nstruct SupportsLongPress: PrimitiveButtonStyle {\n    \n    /// An action to execute on long press\n    let longPressAction: () -> ()\n    \n    /// Whether the button is being pressed\n    @State var isPressed: Bool = false\n    \n    func makeBody(configuration: Configuration) -> some View {\n        \n        // The "label" as specified when declaring the button\n        configuration.label\n        \n            // Visual feedback that the button is being pressed\n            .scaleEffect(self.isPressed ? 0.9 : 1.0)\n        \n            .onTapGesture {\n                \n                // Run the "action" as specified\n                // when declaring the button\n                configuration.trigger()\n                \n            }\n        \n            .onLongPressGesture(\n                \n                perform: {\n                    \n                    // Run the action specified\n                    // when using this style\n                    self.longPressAction()\n                    \n                },\n                \n                onPressingChanged: { pressing in\n                    \n                    // Use "pressing" to infer whether the button\n                    // is being pressed\n                    self.isPressed = pressing\n                    \n                }\n                \n            )\n        \n    }\n    \n}\n\n/// A modifier that applies the `SupportsLongPress` style to buttons\nstruct SupportsLongPressModifier: ViewModifier {\n    let longPressAction: () -> ()\n    func body(content: Content) -> some View {\n        content.buttonStyle(SupportsLongPress(longPressAction: self.longPressAction))\n    }\n}\n\n/// Extend the View protocol for a SwiftUI-like shorthand version\nextension View {\n    func supportsLongPress(longPressAction: @escaping () -> ()) -> some View {\n        modifier(SupportsLongPressModifier(longPressAction: longPressAction))\n    }\n}\n\n// --- At the point of use:\n\nstruct MyCustomButtonRoom: View {\n    \n    var body: some View {\n        \n        Button(\n            action: {\n                print("You\'ve tapped me!")\n            },\n            label: {\n                Text("Do you dare interact?")\n            }\n        )\n        .supportsLongPress {\n            print("Looks like you\'ve pressed me.")\n        }\n        \n    }\n    \n}\n
Run Code Online (Sandbox Code Playgroud)\n


Kev*_*vin 6

这是我使用修饰符的实现:

struct TapAndLongPressModifier: ViewModifier {
  @State private var isLongPressing = false
  let tapAction: (()->())
  let longPressAction: (()->())
  func body(content: Content) -> some View {
    content
      .scaleEffect(isLongPressing ? 0.95 : 1.0)
      .onLongPressGesture(minimumDuration: 1.0, pressing: { (isPressing) in
        withAnimation {
          isLongPressing = isPressing
          print(isPressing)
        }
      }, perform: {
        longPressAction()
      })
      .simultaneousGesture(
        TapGesture()
          .onEnded { _ in
            tapAction()
          }
      )
  }
}
Run Code Online (Sandbox Code Playgroud)

在任何视图上都像这样使用它:

.modifier(TapAndLongPressModifier(tapAction: { <tap action> },
                                  longPressAction: { <long press action> }))
Run Code Online (Sandbox Code Playgroud)

它只是通过稍微缩小视图来模仿按钮的外观。您可以在后面添加任何您想要的其他效果,scaleEffect使其在按下时看起来像您想要的那样。


Jen*_*sie 6

结合高优先级手势和同步手势应该可以解决问题。

Button(action: {})
{
    Text("A Button")
}
.simultaneousGesture(
    LongPressGesture()
        .onEnded { _ in
            print("Loooong")
        }
)
.highPriorityGesture(TapGesture()
                        .onEnded { _ in
                            print("Tap")
                        })
Run Code Online (Sandbox Code Playgroud)

在与其他视图交互时也发现这是一个方便的模式。

  • 这似乎消除了点击时的按钮动画,至少从 iOS 16.4 开始是这样。你知道如何避免这种情况吗? (2认同)

Ash*_*nna 5

我必须为我正在构建的应用程序执行此操作,所以只是想分享。请参阅底部的代码,它相对不言自明,并且符合 SwiftUI 的主要元素。

这个答案与上面的答案之间的主要区别在于,这允许根据状态更新按钮的背景颜色,并且还涵盖了希望在手指抬起后而不是在时间阈值时发生长按动作的用例已通过。

正如其他人所指出的,我无法直接将手势应用于按钮,而必须将它们应用于其中的文本视图。这有一个不幸的副作用,即减少按钮的“点击框”,如果我按下按钮边缘附近,则手势不会触发。因此,我删除了按钮并专注于直接操作我的文本视图对象(可以用图像视图或其他视图替换(但不是按钮!))。

下面的代码设置了三个手势:

  1. 立即触发并反映问题中的“点击”手势的 LongPressGesture (我尚未测试,但这可能可以用 TapGesture 替换)

  2. 另一个 LongPressGesture 的最小持续时间为 0.25,并反映问题中的“长按”手势

  3. 最小距离为 0 的拖动手势允许我们在手指从按钮抬起时执行事件,而不是在 0.25 秒时自动执行(如果这不是您的用例,您可以将其删除)。您可以在此处阅读有关此内容的更多信息:如何检测没有移动或持续时间的 SwiftUI touchDown 事件?

我们对手势进行排序如下:使用“Exclusively”将“长按”(即上面的 2 和 3 组合)和 Tap(上面的第一个手势)组合起来,如果没有达到“长按”的 0.25 秒阈值,则执行点击手势。“长按”本身是我们的长按手势和拖动手势的序列,因此只有在抬起手指后才会执行该操作。

我还在下面添加了代码,用于根据状态更新按钮的颜色。需要注意的一件小事情是,我必须将按钮颜色的代码添加到长按和拖动手势的 onEnded 部分,因为极小的处理时间不幸会导致按钮在 longPressGesture 和 DragGesture 之间切换回 darkButton 颜色(理论上这不应该发生,除非我在某个地方有错误!)。

您可以在此处阅读有关手势的更多信息:https://developer.apple.com/documentation/swiftui/gestures/composition_swiftui_gestures

如果您修改以下内容并注意Apple关于手势的注释(这个答案也很有用,阅读:How to fire event handler when the user STOPS a Long Press Gesture in SwiftUI?)您应该能够设置复杂的自定义按钮交互。使用手势作为构建块并将它们组合起来以消除单个手势中的任何缺陷(例如,longPressGesture 没有选项在其结束时执行事件,而不是在达到条件时执行事件)。

PS我有一个全局环境对象“dataRouter”(这与问题无关,也与我如何选择在我的快速视图中共享参数无关),您可以安全地对其进行编辑。

struct AdvanceButton: View {

@EnvironmentObject var dataRouter: DataRouter

@State var width: CGFloat
@State var height: CGFloat
@State var bgColor: Color

@GestureState var longPress = false
@GestureState var longDrag = false

var body: some View {

    let longPressGestureDelay = DragGesture(minimumDistance: 0)
        .updating($longDrag) { currentstate, gestureState, transaction in
                gestureState = true
        }
    .onEnded { value in
        print(value.translation) // We can use value.translation to see how far away our finger moved and accordingly cancel the action (code not shown here)
        print("long press action goes here")
        self.bgColor = self.dataRouter.darkButton
    }

    let shortPressGesture = LongPressGesture(minimumDuration: 0)
    .onEnded { _ in
        print("short press goes here")
    }

    let longTapGesture = LongPressGesture(minimumDuration: 0.25)
        .updating($longPress) { currentstate, gestureState, transaction in
            gestureState = true
    }
    .onEnded { _ in
        self.bgColor = self.dataRouter.lightButton
    }

    let tapBeforeLongGestures = longTapGesture.sequenced(before:longPressGestureDelay).exclusively(before: shortPressGesture)

    return
        Text("9")
            .font(self.dataRouter.fontStyle)
            .foregroundColor(self.dataRouter.darkButtonText)
            .frame(width: width, height: height)
            .background(self.longPress ? self.dataRouter.lightButton : (self.longDrag ? self.dataRouter.brightButton : self.bgColor))
            .cornerRadius(15)
            .gesture(tapBeforeLongGestures)

    }

}
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢,这正是我正在寻找的行为。另外,我可以确认,至少在我的情况下,使用 TapGesture 不起作用 (3认同)

Kil*_*ian 2

这尚未经过测试,但您可以尝试LongPressGesture向按钮添加 a 。

它大概看起来像这样。

struct ContentView: View {
    @GestureState var isLongPressed = false

    var body: some View {
        let longPress = LongPressGesture()
            .updating($isLongPressed) { value, state, transaction in
                state = value
            }

        return Button(/*...*/)
            .gesture(longPress)
    }
}
Run Code Online (Sandbox Code Playgroud)