倒数计时器发布者 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
属性 不是绑定。
下面是我的倒数计时器和循环进度条代码。
我编写了一个函数 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
属性 不是绑定。