倒数计时器发布者 SwiftUI 的循环进度条

Circular progress bar for a countdown timer publisher SwiftUI

下面是我的倒数计时器和循环进度条代码。

我编写了一个函数 makeProgressIncrement() 来确定 计时器总计 timeSelected.

的每秒进度

更新 ProgressBar 的最佳方法是什么,以便随着倒数计时器发布者的增加而增加?

我应该使用 onReceive 方法吗?

非常感谢任何帮助。

ContentView

import SwiftUI
import Combine

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()


                }
            }
        }
    }
 }

进度条

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

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: Double = 0
    
    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
    }
    
    func makeProgressIncrement() -> CGFloat{
        
        progressIncrement = 1 / timeSelected
        
        return CGFloat(progressIncrement)
        
    }        
}

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
    }
}

您可以在 TimeManager 中创建计算进度的 属性:

extension TimerManager {
  var progress: CGFloat {
     return CGFloat(timeRemaining / timeSelected)
  }
}

但是你还需要一个触发器来让观察者告诉他们它已经改变了。

因为这个值取决于 timeRemaining 属性,也就是 @Published,它会起作用,因为观察对象会注意到变化并再次请求计算值(这也会改变)。

或者,您可以在 .sink 中调用 self.objectWillChange.send() 来通知对象将发生变化,这将完成同样的事情。

一旦你有了它,你就可以直接在你的视图中引用它了:

ProgressBar(progress: self.timer.progress)

(并更改 ProgressBar 使其 .progress 属性 不是绑定。