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
})
}
}
Two things:
self.timer?.invalidate()
..onEnded
of 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
.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 = 0
@State var carbAmount = 0
@State var fatAmount = 0
let maxValue = 50
let minValue = 0
@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 longPressGestureIncrease = LongPressGesture(minimumDuration: 0.2)
.onEnded { value in
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
if proteinAmount < maxValue {
proteinAmount += 1
}
})
print("Timer started")
}
// a long press gesture to activate timer and start decreasing the proteinAmount
let longPressGestureDecrease = LongPressGesture(minimumDuration: 0.2)
.onEnded { value in
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
if proteinAmount > minValue {
proteinAmount -= 1
}
})
print("Timer started")
}
// a combined gesture that forces the user to long press before releasing for increasing the value
let combinedIncrease = longPressGestureIncrease.sequenced(before: releaseGesture)
// a combined gesture that forces the user to long press before releasing for decreasing the value
let combinedDecrease = longPressGestureDecrease.sequenced(before: releaseGesture)
VStack{
VStack{
Text("Food Name")
TextField("", text: $foodName)
.multilineTextAlignment(.center)
.border(.white)
.padding(.trailing, 10)
.frame(width:100, height:10)
}
HStack{
Image(systemName: "minus.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.foregroundColor(.red)
.gesture(combinedDecrease)
Text(String(proteinAmount) + "g")
.frame(width:50, height:50)
Image(systemName: "plus.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.foregroundColor(.red)
.gesture(combinedIncrease)
}
}
}
}
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):