in SwiftUI

Make a Press and Hold ‘Fast Forward’ Button in SwiftUI

I was looking for a way to perform an action while the user was pressing and holding a button in a SwiftUI app. One of the harder parts of researching this issue was figuring out what this pattern was called: ‘press and hold’, ‘fast forward’, ‘rapid fire’, something else?

In Swift, we are using the LongPressGesture, so I suppose that’s appropriate. But, with LongPress, the event is reported once, then that’s it. I also needed a way to stop the action, once the user releases the buton.

Anyway, here is what I wanted to accomplish:

  1. When User taps a button once, and the counter goes up by one.
  2. WhenUser presses and holds the button, the counter starts going up quickly
  3. When User releases the ‘long press’, the counter stops going up.

To solve this in SwiftUI, we can use a regular button and its single tap action for item 1. For item 2, we’ll add a LongPressGesture using .simultaneousGesture() to track the start of a long press. To get the fast forward to stop in item 3, we’ll rely on an additional single tap event generated the button when it is released from the long gesture.

To power the fast forward counting during the long press, we’ll make use of a Timer.

What It Looks Like

The Full Code

struct ContentView: View {
    @State private var counter: Int = 0
    @State private var timer: Timer?
    @State var isLongPressing = false
    
    var body: some View {
        VStack {
            Text("\(counter)")
                .font(.largeTitle)
                .fontWeight(.heavy)
            
            Button(action: {
                print("tap")
                if(self.isLongPressing){
                    //this tap was caused by the end of a longpress gesture, so stop our fastforwarding
                    self.isLongPressing.toggle()
                    self.timer?.invalidate()
                    
                } else {
                    //just a regular tap
                    self.counter += 1
                    
                }
            }, label: {
                Image(systemName: self.isLongPressing ? "chevron.right.2": "chevron.right")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                
            })
            .simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onEnded { _ in
                print("long press")
                self.isLongPressing = true
                //or fastforward has started to start the timer
                self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
                    self.counter += 1
                })
            })
        }
    }
}