大家好,我是阿树,一个独立开发者。
在开发「早晨计划」的过程中,有一个番茄钟的功能需要开发,这里写下我的实现。
番茄钟简介
番茄工作法是简单易行的时间管理方法。使用番茄工作法,选择一个待完成的任务,将番茄时间设为25分钟,专注工作,中途不允许做任何与该任务无关的事,直到番茄时钟响起,然后进行短暂休息一下(5分钟就行),然后再开始下一个番茄。
界面如下:
状态机
状态机如上,一共五个状态,还是有点小复杂的。
番茄Model与时间的存储设计
这里比较重要的是时间我拆分了两个字段去存储passTime和currTimeStamp。
enum TomatoState: Int {
case stop = 1
case runing = 2
case pause = 3
case breaking = 4
case breakingStop = 5
}
class TomatoModel: Equatable,ObservableObject {
var id: Int = 0
var taskId: Int = 0
@Published var currNum: Int = 0
@Published var targetNum: Int = 0
@Published var state: Int = TomatoState.stop.rawValue
@Published var time: Double = 0 // 已经过去多少时间
@Published var timestamp: Double = 0 // 上次操作的timestamp
static func == (lhs: TomatoModel, rhs: TomatoModel) -> Bool {
return lhs.id == rhs.id
}
}
其中passTime是用户点击暂停按钮之前秒数,currTimeStamp是用户点击继续番茄的时间戳。之所以这么做是因为如果只存储秒数的话,app进入后台或者app关闭后定时器无法继续工作。只存储的时间戳的话就没办法支持暂停功能。所以暂停之前的时间用秒数存储,正在进行中的时间用时间戳去存储。
那么番茄钟的时间就应该是:
let time = tomato.time + Date.Now().timeIntervalSince1970 - tomato.timestamp
界面代码
圆环进度条的制作用的是UICircularProgressRing开源库,
ZStack{
ProgressRing(
progress: .constant(.percent(progress)),
axis: .top,
clockwise: true,
outerRingStyle: .init(
color: .color(Color(UIColor.secondarySystemBackground)),
strokeStyle: .init(lineWidth: 4)
),
innerRingStyle: .init(
color: .color(.iPrimary),
strokeStyle: .init(lineWidth: 4)
)
){_ in
Text("")
}
.animation(nil)
.frame(width: ringSize, height: ringSize)
Image(systemName: "circle.fill")
.resizable()
.foregroundColor(Color.iPrimary)
.frame(width:10,height:10)
.offset( x: 0, y: -ringSize/2 + 2)
.rotationEffect(.init(degrees: progress * 360))
.animation(nil)
Text(getTimeString())
.font(Font.system(size: 50))
.fontWeight(.light)
.onReceive(timer){ t in
updateTimer()
}
}
}
定时器代码
func updateTimer()
{
if tomato.state == TomatoState.runing.rawValue{
let time = tomato.time + Date.Now().timeIntervalSince1970 - tomato.timestamp
progress = time / TaskTomatoPage.TomatoRuningSec
if time >= 25 * 60 {
tomato.state = TomatoState.breakingStop.rawValue
tomato.time = 0
tomato.timestamp = 0
tomato.currNum += 1
progress = 100.0
saveTomato()
}
}else if tomato.state == TomatoState.breaking.rawValue {
let time = Date.Now().timeIntervalSince1970 - tomato.timestamp
progress = time / TaskTomatoPage.TomatoBreakingSec
if time >= 5 * 60 {
tomato.state = TomatoState.stop.rawValue
tomato.time = 0
tomato.timestamp = 0
progress = 0
saveTomato()
}
}
}
这里提供了我自己实现番茄钟的思路,因为完整代码放这感觉太啰嗦,读者应该可以自己实现,享受编码乐趣。
最后,如果有兴趣想要完整代码,或者聊聊独立开发相关的,可以联系微信:zhijzan。