I would like to develop an app that includes a timer - everything works fine so far - yet I have the problem that the CircleProgress is not quite at 0 when the counter is. (as you can see in the picture below)
Long story short - my timer is not precise... How can I make it better?
So this is my code:
This is the View where I give my Binding to the ProgressCircleView:
struct TimerView: View {
//every Second change Circle and Value (Circle is small because of the animation)
let timerForText = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let timerForCircle = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
@State var progress : Double = 1.0
@State var counterCircle : Double = 0.0
@State var counterText : Int = 0
@State var timerSeconds : Int = 60
let customInverval : Int
var body: some View {
ZStack{
ProgressCircleView(progress: self.$progress, timerSeconds: self.$timerSeconds, customInterval: customInverval)
.padding()
.onReceive(timerForCircle){ time in
//if counterCircle is the same value as Interval -> break
if self.counterCircle == Double(customInverval){
self.timerForCircle.upstream.connect().cancel()
} else {
decreaseProgress()
}
counterCircle += 0.001
}
VStack{
Text("\(timerSeconds)")
.font(.system(size: 80))
.bold()
.onReceive(timerForText){time in
//wenn counterText is the same value as Interval -> break
if self.counterText == customInverval{
self.timerForText.upstream.connect().cancel()
} else {
incrementTimer()
print("timerSeconds: \(self.timerSeconds)")
}
counterText += 1
}.multilineTextAlignment(.center)
}
.accessibilityElement(children: .combine)
}.padding()
}
func decreaseProgress() -> Void {
let decreaseValue : Double = 1/(Double(customInverval)*1000)
self.progress -= decreaseValue
}
func incrementTimer() -> Void {
let decreaseValue = 1
self.timerSeconds -= decreaseValue
}
}
And this is my CircleProgressClass:
struct ProgressCircleView: View {
@Binding var progress : Double
@Binding var timerSeconds : Int
let customInterval : Int
var body: some View {
ZStack{
Circle()
.stroke(lineWidth: 25)
.opacity(0.08)
.foregroundColor(.black)
Circle()
.trim(from: 0.0, to: CGFloat(Double(min(progress, 1.0))))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.rotationEffect(.degrees(270.0))
.foregroundColor(getCircleColor(timerSeconds: timerSeconds))
.animation(.linear)
}
}
}
func getCircleColor(timerSeconds: Int) -> Color {
if (timerSeconds <= 10 && timerSeconds > 3) {
return Color.yellow
} else if (timerSeconds <= 3){
return Color.red
} else {
return Color.green
}
}
You cannot control the timer, it will never be entirely accurate.
Instead, I suggest you save the end date and calculate your progress based on it:
struct TimerView: View {
//every Second change Circle and Value (Circle is small because of the animation)
let timerForText = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let timerForCircle = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
@State var progress : Double = 1.0
@State var timerSeconds : Int = 60
@State var endDate: Date? = nil
let customInverval : Int
var body: some View {
ZStack{
ProgressCircleView(progress: self.$progress, timerSeconds: self.$timerSeconds, customInterval: customInverval)
.padding()
.onReceive(timerForCircle){ _ in
decreaseProgress()
}
VStack{
Text("\(timerSeconds)")
.font(.system(size: 80))
.bold()
.onReceive(timerForText){ _ in
incrementTimer()
}.multilineTextAlignment(.center)
}
.accessibilityElement(children: .combine)
}.padding()
.onAppear {
endDate = Date(timeIntervalSinceNow: TimeInterval(customInverval))
}
}
func decreaseProgress() -> Void {
guard let endDate = endDate else { return}
progress = max(0, endDate.timeIntervalSinceNow / TimeInterval(customInverval))
if endDate.timeIntervalSinceNow <= 0 {
timerForCircle.upstream.connect().cancel()
}
}
func incrementTimer() -> Void {
guard let endDate = endDate else { return}
timerSeconds = max(0, Int(endDate.timeIntervalSinceNow.rounded()))
if endDate.timeIntervalSinceNow <= 0 {
timerForText.upstream.connect().cancel()
print("stop")
}
}
}