问题描述
下面是我的倒数计时器和循环进度条代码。
我编写了一个函数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
属性不受约束。