Timer 用于倒计时的循环进度条publisher SwiftUI
下面是我的倒计时和循环进度条代码 我编写了一个函数Timer 用于倒计时的循环进度条publisher SwiftUI,timer,swiftui,progress-bar,combine,Timer,Swiftui,Progress Bar,Combine,下面是我的倒计时和循环进度条代码 我编写了一个函数makeProgressIncrement(),用于确定 计时器每秒的进度总计timeSelected 更新ProgressBar使其随倒计时时间增加的最佳方法是什么 我应该使用onReceive方法吗 非常感谢您的帮助 ContentView import SwiftUI import Combine struct ContentView: View { @StateObject var timer = TimerManager() @St
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()
}
}
}
}
}
ProgressBar
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)
}
}
}
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
}
}
TimerManager
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)
}
}
}
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
中创建计算进度的computed属性:
extension TimerManager {
var progress: CGFloat {
return CGFloat(timeRemaining / timeSelected)
}
}
但你也需要一个触发器,让观察者告诉他们它已经改变了
由于该值取决于timeRemaining
属性,该属性是@Published
,因此它将起作用,因为观察对象将注意到一个更改并再次请求计算值(该值也会更改)
或者,您可以在.sink
中调用self.objectWillChange.send()
,通知对象将发生更改,这将完成相同的任务
一旦有了它,您可以在视图中直接引用它:
ProgressBar(progress: self.timer.progress)
(并更改
ProgressBar
,使其.progress
属性不是绑定。您可以使用.onReceive
。或者,您可以将其作为timermager
类的计算属性并直接使用,例如:self.$timer.progressIncrement
@NewDev>.onReceive但我收到一个错误,该错误与“Publisher”不符。由于类中有progressIncrement
,我如何直接使用它来更新ProgressBarprogressValue
?感谢您的帮助。我使用计算属性得到以下错误:无法分配到属性:“progress”是仅获取属性。我尝试了获取/设置,但进度条仍不能在计时器倒计时的每一秒正确更新。您到底在分配什么?其思想是此属性是根据timeSelected
计算的,因此您不应该为其分配任何内容-只需设置timeSelected
进度条即可(progress:self.$timer.progress)
在尝试传递计时器时抛出错误。我明白了。ProgressBar
接受它作为绑定。但是为什么?除非我缺少什么,否则它应该只是一个常规参数。(我更改了答案)如果我将ProgressBar
结构中的变量更改为非绑定,则错误将消失,但进度条仍与计时器不同步。它仅在启动timer.start()
时触发一次。