I'm creating a custom stepper control in SwiftUI, and I'm trying to replicate the accelerating value change behavior of the built-in control. In a SwiftUI Stepper, long pressing on "+" or "-" will keep increasing/decreasing the value with the rate of change getting faster the longer you hold the button.
I can create the visual effect of holding down the button with the following:
struct PressBox: View {
@GestureState var pressed = false
@State var value = 0
var body: some View {
ZStack {
Rectangle()
.fill(pressed ? Color.blue : Color.green)
.frame(width: 70, height: 50)
.gesture(LongPressGesture(minimumDuration: .infinity)
.updating($pressed) { value, state, transaction in
state = value
}
.onChanged { _ in
self.value += 1
}
)
Text("\(value)")
.foregroundColor(.white)
}
}
}
This only increments the value once. Adding a timer publisher to the onChanged modifier for the gesture like this:
let timer = Timer.publish(every: 0.5, on: .main, in: .common)
@State var cancellable: AnyCancellable? = nil
...
.onChanged { _ in
self.cancellable = self.timer.connect() as? AnyCancellable
}
will replicate the changing values, but since the gesture never completes successfully (onEnded will never be called), there's no way to stop the timer. Gestures don't have an onCancelled modifier.
I also tried doing this with a TapGesture which would work for detecting the end of the gesture, but I don't see a way to detect the start of the gesture. This code:
.gesture(TapGesture()
.updating($pressed) { value, state, transaction in
state = value
}
)
generates an error on $pressed:
Cannot convert value of type 'GestureState' to expected argument type 'GestureState<_>'
Is there a way to replicate the behavior without falling back to UIKit?