How to achieve UIKit's "touch down" in SwiftUI?

I want my button to fire when the finger touches the button, not when the finger leaves the button.

A common answer I see on StackOverflow is to use a DragGesture and set minimumDistance to 0. This does trigger the button when the finger touches the button. However, when the finger is pressed down, if I move my finger even just a little bit, the button would get triggered again, which not something that I want. I want the button to fire only when the finger first touches the button, but not when the finger drags the button.

How can I achieve this touch down behavior in SwiftUI?

Thanks in advance.

This isn't an obvious thing to do, but if you play around with the way Button is designed you find out that it uses either PrimitiveButtonStyle or ButtonStyle protocols.

I would probably go with PrimitiveButtonStyle and implement a custom style for your button.

In the end you will have something like this:

Button(...)
  .buttonStyle(MyButtonStyle(/* here you can have expose anything you want */))

This approach allows you to create buttons with custom styles that implement custom gestures.

Here is a quick prototype, but it looses the default button Style.

struct MyButtonStyle: PrimitiveButtonStyle {
  func makeBody(configuration: Configuration) -> some View {
    configuration
      .label
      .onLongPressGesture(
        minimumDuration: 0,
        perform: configuration.trigger
      )
  }
}

struct ContentView: View {
  var body: some View {
    VStack {
      Button("A") {
        print("A pressed")
      }
      Button
        .init("B") {
          print("B pressed")
        }
        .buttonStyle(MyButtonStyle())
    }
  }
}
6 Likes

YOU ARE A LIFE SAVER!
It worked! Can't thank you enough!!!

1 Like

See my Stack Overflow answer here, where I achieved this with the _onButtonGesture method on View.

Code:

struct ContentView: View {
    @State private var counter = 0
    @State private var pressing = false

    var body: some View {
        VStack(spacing: 30) {
            Text("Count: \(counter)")

            Button("Increment") {
                counter += 1
            }
            ._onButtonGesture { pressing in
                self.pressing = pressing
            } perform: {}

            Text("Pressing button: \(pressing ? "yes" : "no")")
        }
    }
}
2 Likes

This is not recomended, because you're using private API which could result in your application being rejected on the app store.

1 Like

Or you could do it with the onLongPress modifier:

.onLongPressGesture(minimumDuration: Double, maximumDistance: CGFLoat, perform: () -> Void, onPressingChanged: (Bool) -> Void)?)

This might not be the best implementation but works perfectly. Only solution in which the 'tap' is not colliding with scroll view's drag gesture.

1 Like