3

I'm developing a simple MIDI keyboard. Each piano key is a button. As soon you press it, it sends a "MIDI note ON" signal to a virtual device:

Button(action: {
    MidiDevice.playNote("C")
}) { 
    Image(systemName: "piano-white-key")
}

It works fine. The latency is good and the user can play the key for just a fraction of a second or hold the button for longer notes. Now, how do I intercept the "user has lifted her finger" action in order to immediately send the MidiDevice.stopNote("C") event?

pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • Is anything here useful to you? https://stackoverflow.com/questions/58284994/swiftui-how-to-handle-both-tap-long-press-of-button – John Nimis Feb 23 '21 at 19:04

2 Answers2

3

Here is possible solution (as far as I understood your goal) - to use ButtonStyle to detect isPressed state. Standard Button sends actions of tap UP, so we just add action handler for tap DOWN.

Tested with Xcode 12.4 / iOS 14.4

struct ButtonPressHandler: ButtonStyle {
    var action: () -> ()
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .foregroundColor(configuration.isPressed ? 
                  Color.blue.opacity(0.7) : Color.blue)   // just to look like system
            .onChange(of: configuration.isPressed) {
                if $0 {
                    action()
                }
            }
    }
}

struct TestButtonPress: View {
    var body: some View {
        Button(action: {
            print(">> tap up")
        }) {
            Image(systemName: "piano-white-key")
        }
        .buttonStyle(ButtonPressHandler {
            print("<< tap down")
        })
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
1

In addition to Asperi's answer, you can create an extension which will make it more SwiftUI-style:

extension Button {
    func onTapEnded(_ action: @escaping () -> Void) -> some View {
        buttonStyle(ButtonPressHandler(action: action))
    }
}
struct ContentView: View {
    var body: some View {
        Button(action: {
            print(">> tap up")
        }) {
            Image(systemName: "piano-white-key")
        }
        .onTapEnded {
            print("<< tap down")
        }
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209