SwiftUI - 如何制作启动/停止计时器

问题描述 投票:0回答:3

我的目标是在 SwiftUI 中创建一个从 0 开始的视图。当您按下视图时,计时器应该开始向上计数,再次点击会停止计时器。最后,当您再次点击启动计时器时,计时器应从 0 开始。

这是我当前的代码:

import SwiftUI

struct TimerView: View {
    @State var isTimerRunning = false
    @State private var endTime = Date()
    @State private var startTime =  Date()
    let timer = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
    
    var tap: some Gesture {
        TapGesture(count: 1)
            .onEnded({
                isTimerRunning.toggle()
            })
    }

    var body: some View {

        Text("\(endTime.timeIntervalSince1970 - startTime.timeIntervalSince1970)")
            .font(.largeTitle)
            .gesture(tap)
            .onReceive(timer) { input in
                startTime = isTimerRunning ? startTime : Date()
                endTime = isTimerRunning ? input : endTime
            }

    }
}

此代码使计时器立即启动并且永远不会停止,即使我点击它也是如此。计时器也会向后(进入负数)而不是向前。

有人可以帮我理解我做错了什么吗?另外,我想知道这对于计时器来说是否是一个好的整体策略(使用 Timer.publish)。

谢谢!

swift view timer swiftui
3个回答
34
投票

这是一个固定版本。看看我所做的更改。

    如果计时器正在运行,
  • .onReceive
    现在会更新
    timerString
    。 timeString 是现在(即
    Date()
    )和
    startTime
    之间的时间间隔。
  • 如果计时器未运行,则点击计时器可设置
    startTime

struct TimerView: View {
    @State var isTimerRunning = false
    @State private var startTime =  Date()
    @State private var timerString = "0.00"
    let timer = Timer.publish(every: 0.01, 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 = String(format: "%.2f", (Date().timeIntervalSince( self.startTime)))
                }
            }
            .onTapGesture {
                if !isTimerRunning {
                    timerString = "0.00"
                    startTime = Date()
                }
                isTimerRunning.toggle()
            }
    }
}

上面的版本虽然简单,但令我烦恼的是

Timer
一直在发布。我们只需要在计时器运行时发布
Timer

这是一个启动和停止

Timer
的版本:

struct TimerView: View {
    @State var isTimerRunning = false
    @State private var startTime =  Date()
    @State private var timerString = "0.00"
    @State private var 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 = String(format: "%.2f", (Date().timeIntervalSince( self.startTime)))
                }
            }
            .onTapGesture {
                if isTimerRunning {
                    // stop UI updates
                    self.stopTimer()
                } else {
                    timerString = "0.00"
                    startTime = Date()
                    // start UI updates
                    self.startTimer()
                }
                isTimerRunning.toggle()
            }
            .onAppear() {
                // no need for UI updates at startup
                self.stopTimer()
            }
    }
    
    func stopTimer() {
        self.timer.upstream.connect().cancel()
    }
    
    func startTimer() {
        self.timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
    }
}

3
投票

秒表定时器

以下方法允许您使用

start
stop
属性包装器以及
reset
协议创建
@Published
/
@ObservedObject
/
ObservableObject
SwiftUI 计时器。

这是 ContentView 结构:

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var stopWatch = Stop_Watch()
    
    var body: some View {
                
        let minutes = String(format: "%02d", stopWatch.counter / 60)
        let seconds = String(format: "%02d", stopWatch.counter % 60)
        let union = minutes + " : " + seconds
        
        ZStack {
            Color.black.ignoresSafeArea()
            VStack {
                Spacer()
                HStack {
                    Button("Start") { self.stopWatch.start() }
                        .foregroundStyle(.purple)
                    Button("Stop") { self.stopWatch.stop() }
                        .foregroundStyle(.orange)
                    Button("Reset") { self.stopWatch.reset() }
                        .foregroundStyle(.yellow)
                }
                Spacer()
                Text("\(union)")
                    .foregroundStyle(.teal)
                    .font(.custom("", size: 90))
                Spacer()
            }
        }
    }
}

...和 Stop_Watch 类:

class Stop_Watch: ObservableObject {
    
    @Published var counter: Int = 0
    var timer = Timer()
    
    func start() {
        self.timer = Timer.scheduledTimer(withTimeInterval: 1.0,
                                                   repeats: true) { _ in
            self.counter += 1
        }
    }
    func stop() {
        self.timer.invalidate()
    }
    func reset() {
        self.counter = 0
        self.timer.invalidate()
    }
}


1
投票

针对 Swift 5.7 和 iOS 16 进行了更新,以显示一个像简单秒表一样计算秒数和分钟数的计时器。使用 DateComponentsFormatter 格式化分钟和秒。

struct StopWatchView: View {
    @State var isRunning = false
    @State private var startTime = Date()
    @State private var display = "00:00"
    @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    var body: some View {
      Text(display)
        .font(.system(size: 20, weight: isRunning ? .bold : .light, design: .monospaced))
        .foregroundColor(.accentColor)
        .onReceive(timer) { _ in
          if isRunning {
            let duration = Date().timeIntervalSince(startTime)
            let formatter = DateComponentsFormatter()
            formatter.allowedUnits = [.minute, .second]
            formatter.unitsStyle = .positional
            formatter.zeroFormattingBehavior = .pad
            display = formatter.string(from: duration) ?? ""
          }
        }
        .onTapGesture {
          if isRunning {
            stop()
          } else {
            display = "00:00"
            startTime = Date()
            start()
          }
          isRunning.toggle()
        }
        .onAppear {
          stop()
        }
    }
    
    func stop() {
      timer.upstream.connect().cancel()
    }
    func start() {
      timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    }
  }


  struct StopWatchView_Previews: PreviewProvider {
    static var previews: some View {
      StopWatchView()
    }
  }
© www.soinside.com 2019 - 2024. All rights reserved.