Search code examples
swiftxcodetimerswiftuigesture

SwiftUI run code periodically while button is being held down; run different code when it is just tapped?


What I'm trying to do is implement a button that runs a certain line of code every 0.5 seconds while it is being held down (it can be held down indefinitely and thus run the print statement indefinitely). I'd like it to have a different behavior when it is tapped. Here is the code:

struct ContentView: View {
@State var timeRemaining = 0.5
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
@State var userIsPressing = false //detecting whether user is long pressing the screen

var body: some View {
    VStack {
       Image(systemName: "chevron.left").onReceive(self.timer) { _ in
           if self.userIsPressing == true {
             if self.timeRemaining > 0 {
                self.timeRemaining -= 0.5
              }
            //resetting the timer every 0.5 secdonds and executing code whenever //timer reaches 0

     if self.timeRemaining == 0 {
            print("execute this code")
            self.timeRemaining = 0.5
         }
        }
    }.gesture(LongPressGesture(minimumDuration: 0.5)
                   .onChanged() { _ in
                       //when longpressGesture started
                   self.userIsPressing = true
                   }
                   .onEnded() { _ in
                       //when longpressGesture ended
                   self.userIsPressing = false

                   }
                   )
           }
}
}

At the moment, it's sort of reverse of what I need it to do; the code above runs the print statement indefinitely when I click the button once but when I hold it down, it only does the execution once...how can I fix this?


Solution

  • Here is a solution - to get continuous pressing it needs to combine long press gesture with sequenced drag and add timer in handlers.

    Updated: Tested with Xcode 11.4 / iOS 13.4 (in Preview & Simulator)

    struct TimeEventGeneratorView: View {
        var callback: () -> Void
        private let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
    
        var body: some View {
            Color.clear
                .onReceive(self.timer) { _ in
                    self.callback()
                }
        }
    }
    
    struct TestContinuousPress: View {
    
        @GestureState var pressingState = false // will be true till tap hold
        var pressingGesture: some Gesture {
            LongPressGesture(minimumDuration: 0.5).sequenced(before:
                  DragGesture(minimumDistance: 0, coordinateSpace:
                  .local)).updating($pressingState) { value, state, transaction in
                    switch value {
                        case .second(true, nil):
                            state = true
                        default:
                            break
                    }
                }.onEnded { _ in
                }
        }
    
        var body: some View {
            VStack {
                Image(systemName: "chevron.left")
                    .background(Group { if self.pressingState { TimeEventGeneratorView {
                        print(">>>> pressing: \(Date())")
                    }}})
                .gesture(TapGesture().onEnded {
                    print("> just tap ")
                })
                .gesture(pressingGesture)
            }
        }
    }