倒数计时器发布者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,因此它将起作用,因为观察对象将注意到更改并再次请求计算值(该值也会更改)。 / p>

或者,您可以在self.objectWillChange.send()内调用.sink来通知对象将更改,并且将完成相同的操作。

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

ProgressBar(progress: self.timer.progress)

(并更改ProgressBar,以使其.progress属性不受约束。