Synchronize circular progress bar for a countdown timer publisher SwiftUI

Below is my code for a countdown timer and circular progress bar.

I created a variable that determines the progress per second progressIncrement from the timer total of timeSelected.

What is the best way to to update the ProgressBar so it increases and is synchronized with the countdown timer publisher?

Any help is greatly appreciated.

ContentView
Code Block
import Combine
import SwiftUI
struct ContentView: View {
@StateObject var timer = TimerManager()
@State var progressValue : CGFloat = 0
var body: some View {
ZStack{
VStack {
ZStack{
ProgressBar(progress: self.$progressValue)
.frame(width: 300.0, height: 300)
.padding(40.0)
VStack{
Image(systemName: timer.isRunning ? "pause.fill" : "play.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 80, height: 80)
.foregroundColor(.blue)
.onTapGesture{
timer.isRunning ? timer.pause() : timer.start()
}
}
}
Text(timer.timerString)
.onAppear {
if timer.isRunning {
timer.stop()
}
}
.padding(.bottom, 100)
Image(systemName: "stop.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 35, height: 35)
.foregroundColor(.blue)
.onTapGesture{
timer.stop()
}
}
}
}
}

ProgessBar
Code Block
struct ProgressBar: View {
@Binding var progress: CGFloat
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.blue)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.blue)
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear)
}
}
}

TimerManager
Code Block
class TimerManager: ObservableObject {
/// Is the timer running?
@Published private(set) var isRunning = false
/// String to show in UI
@Published private(set) var timerString = ""
/// Timer subscription to receive publisher
private var timer: AnyCancellable?
/// Time that we're counting from & store it when app is in background
private var startTime: Date? { didSet { saveStartTime() } }
var timeSelected: Double = 30
var timeRemaining: Double = 0
var timePaused: Date = Date()
var progressIncrement: CGFloat {
     return CGFloat(1/timeSelected)
  }
init() {
startTime = fetchStartTime()
if startTime != nil {
start()
}
}
}
// MARK: - Public Interface
extension TimerManager {
func start() {
timer?.cancel()
if startTime == nil {
startTime = Date()
}
timerString = ""
timer = Timer
.publish(every: 0, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard
let self = self,
let startTime = self.startTime
else { return }
let now = Date()
let elapsedTime = now.timeIntervalSince(startTime)
self.timeRemaining = self.timeSelected - elapsedTime
guard self.timeRemaining > 0 else {
self.stop()
return
}
self.timerString = String(format: "%0.1f", self.timeRemaining)
}
isRunning = true
}
func stop() {
timer?.cancel()
timeSelected = 300
timer = nil
startTime = nil
isRunning = false
timerString = " "
}
func pause() {
timeSelected = timeRemaining
timer?.cancel()
startTime = nil
timer = nil
isRunning = false
}
}
private extension TimerManager {
func saveStartTime() {
if let startTime = startTime {
UserDefaults.standard.set(startTime, forKey: "startTime")
} else {
UserDefaults.standard.removeObject(forKey: "startTime")
}
}
func fetchStartTime() -> Date? {
UserDefaults.standard.object(forKey: "startTime") as? Date
}
}

Synchronize circular progress bar for a countdown timer publisher SwiftUI
 
 
Q