struct TwoStateButton: View { @State private var isPressed = false let touchDownView: () -> Content let touchUpView: () -> Content let action: () -> () let touchDownAction: (() -> ())? var body: some View { Button(action: action, label: { ZStack { if isPressed { touchDownView() } else { touchUpView() } } }).buttonStyle(CustomButtonStyle(onPressed: { isPressed = true touchDownAction?() }, onReleased: { isPressed = false })) } // https://stackoverflow.com/a/71714195/8047 private struct CustomButtonStyle: ButtonStyle { var onPressed: () -> Void var onReleased: () -> Void // Wrapper for isPressed where we can run custom logic via didSet (or willSet) @State private var isPressedWrapper: Bool = false { didSet { // new value is pressed, old value is not pressed -> switching to pressed state if (isPressedWrapper && !oldValue) { onPressed() } // new value is not pressed, old value is pressed -> switching to unpressed state else if (oldValue && !isPressedWrapper) { onReleased() } } } // return the label unaltered, but add a hook to watch changes in configuration.isPressed func makeBody(configuration: Self.Configuration) -> some View { return configuration.label .onChange(of: configuration.isPressed, perform: { newValue in isPressedWrapper = newValue }) } } }