SwiftUI - Increment number on long press gesture, then stop once gesture is released

Viewed 35

I'm attempting to create a stepper where a number increments rapidly on a longpress gesture and stops when the user releases.

So far, I get the increment to work on the longPress, but when I release the timer still goes, continuing to increment the state.

What can I do to resolve this issue that when the user releases the press, the timer stops.

struct CustomFoodItemView: View {
    @State var foodName = ""
    @State var proteinAmount = 1
    @State var carbAmount = 1
    @State var fatAmount = 1
    
    @State private var timer: Timer?
    @State var isLongPressing = false
    
    var body: some View {
        VStack{
            
            VStack{
                Text("Food Name")
                TextField("", text: $foodName)
                    .multilineTextAlignment(.center)
                    .border(.white)
                    .padding(.trailing, 10)
                    .frame(width:100, height:10)
            }
            HStack{
                Text(String(proteinAmount) + "g")
                    .frame(width:50, height:50)
                
                Image(systemName: "plus.circle.fill")
                    .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 30, height: 30)
                        .foregroundColor(Color("SuccessButtonColor"))
                        .simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onChanged { _ in
                                      print("long press")
                                      self.isLongPressing = true
                            if self.isLongPressing == true{
                                self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
                                    proteinAmount += 1
                                })
                            } 
                                  }
                                .onEnded { _ in
                            print("stopped") //why won't you stop
                                    self.isLongPressing = false
                                })
                       }
              }

enter image description here

1 Answers

Two things:

  1. You don't stop the timer at all, to stop it, you have to invalidate it (as Leo mentioned). You can do that with self.timer?.invalidate().
  2. The .onEndedof the LongPressGesture will be called when the LongPressGesture has been recognized by pressing the button longer than the minimumDuration time, it does not handle the event when the button will be released. So you don't want to stop the timer on .onEnded.

Combined LongPressGesture and DragGesture

I tried your code and an approach could be to use the LongPressGesture to start the timer and a DragGesture with a minimum distance of 0 to recognize the button release.

This would look like:

struct CustomFoodItemView: View {
    @State var foodName = ""
    @State var proteinAmount = 1
    @State var carbAmount = 1
    @State var fatAmount = 1
    
    @State private var timer: Timer?
    
    var body: some View {
        
        // a drag gesture that recognizes the release of the button to stop the timer, set minimumDistance to 0 to ensure no dragging is required
        let releaseGesture = DragGesture(minimumDistance: 0)
            .onEnded { _ in
                self.timer?.invalidate()
                print("Timer stopped")
            }
        
        // a long press gesture to activate timer and start increasing the proteinAmount
        let longPressGesture = LongPressGesture(minimumDuration: 0.2)
            .onEnded { value in
                self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
                    proteinAmount += 1
                })
                print("Timer started")
            }
        
        // a combined gesture that forces the user to long press before releasing
        let combined = longPressGesture.sequenced(before: releaseGesture)
        
        VStack{
            
            VStack{
                Text("Food Name")
                TextField("", text: $foodName)
                    .multilineTextAlignment(.center)
                    .border(.white)
                    .padding(.trailing, 10)
                    .frame(width:100, height:10)
            }
            HStack{
                Text(String(proteinAmount) + "g")
                    .frame(width:50, height:50)
                
                Image(systemName: "plus.circle.fill")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 30, height: 30)
                    .foregroundColor(Color("SuccessButtonColor"))
                    .gesture(combined)
            }
        }
    }
}

Please see the gif with the the few items from the code snippet (the number increases after starting the long press and stops when releasing the "+" button):

Increasing number by activated timer

Related