如何制作一个计时器来显示在 SwiftUI 中已经过了多少时间?

How to make a timer that shows how much time has passed in SwiftUI?

我正在尝试在我的视图顶部添加一个计时器,以显示该视图打开了多长时间。

到目前为止,这是我所拥有的:

@State var isTimerRunning = false
@State private var startTime =  Date()
@State private var timerString = "0:0"
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

Text(self.timerString)
    .font(Font.system(.largeTitle, design: .monospaced))
    .onReceive(timer) { _ in
        if self.isTimerRunning {
            timerString = String(format: "%.2f", (Date().timeIntervalSince( self.startTime)))
            }
        }
     .onAppear() {
         if !isTimerRunning {
             timerString = "0:0"
             startTime = Date()
         }
         isTimerRunning.toggle()
     }

然而,当我希望它以“12:43”的形式显示秒和分钟时,它以“1.32434234234234”的形式显示毫秒和秒。

使用dateComponents获取开始时间和当前时间之间的差异。

extension Date {
    func passedTime(from date: Date) -> String {
        let difference = Calendar.current.dateComponents([.minute, .second], from: date, to: self)
        
        let strMin = String(format: "%02d", difference.minute ?? 00)
        let strSec = String(format: "%02d", difference.second ?? 00)
        
        return "\(strMin):\(strSec)"
    }
}

并且在视图中

struct ContentView: View {
    @State private var isTimerRunning = false
    @State private var startTime =  Date()
    @State private var timerString = "00:00"
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        Text(self.timerString)
            .font(Font.system(.largeTitle, design: .monospaced))
            .onReceive(timer) { _ in
                if self.isTimerRunning {
                    timerString = Date().passedTime(from: startTime)
                }
            }
            .onAppear() {
                if !isTimerRunning {
                    timerString = "0:0"
                    startTime = Date()
                }
                isTimerRunning.toggle()
            }
    }
}

您可以为此使用 TimeInterval 的扩展。您可以通过更改 formatter.unitsStyle.positional 将显示 00:00:00,而 .abbreviated 将显示 0h 0m 0s)和 formatter.zeroFormattingBehavior 变量来很好地自定义字符串。

的致谢名单。

extension TimeInterval {
    func format(using units: NSCalendar.Unit) -> String {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = units
        formatter.unitsStyle = .positional
        formatter.zeroFormattingBehavior = .pad
        return formatter.string(from: self) ?? ""
    }
}

struct TimerPlayground: View {
    @State var isTimerRunning = false
    @State private var startTime =  Date()
    @State var interval = TimeInterval()
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        Text(interval.format(using: [.hour, .minute, .second]))
            .font(Font.system(.largeTitle, design: .monospaced))
            .onReceive(timer) { _ in
                if self.isTimerRunning {
                    interval = Date().timeIntervalSince(startTime)
                }
            }
             .onAppear() {
                 if !isTimerRunning {
                     startTime = Date()
                 }
                 isTimerRunning.toggle()
             }
    }
}

struct TimerPlayground_Previews: PreviewProvider {
    static var previews: some View {
        TimerPlayground()
    }
}

与@Duncan C 的评论一致,这是一个更新版本,它只创建一次格式化程序(本地)以获得更好的性能。

struct TimerPlayground: View {
    @State var isTimerRunning = false
    @State private var startTime =  Date()
    @State var interval = TimeInterval()
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @State var formatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .pad
        return formatter
    }()
    
    var body: some View {
        Text(formatter.string(from: interval) ?? "")
            .font(Font.system(.largeTitle, design: .monospaced))
            .onReceive(timer) { _ in
                if self.isTimerRunning {
                    interval = Date().timeIntervalSince(startTime)
                }
            }
             .onAppear() {
                 if !isTimerRunning {
                     startTime = Date()
                 }
                 isTimerRunning.toggle()
             }
    }
}