SwiftUI 如何实现番茄钟功能

984 阅读2分钟

大家好,我是阿树,一个独立开发者。

在开发「早晨计划」的过程中,有一个番茄钟的功能需要开发,这里写下我的实现。

番茄钟简介

番茄工作法是简单易行的时间管理方法。使用番茄工作法,选择一个待完成的任务,将番茄时间设为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。