我的目标是在 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)。
谢谢!
这是一个固定版本。看看我所做的更改。
.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()
}
}
以下方法允许您使用
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()
}
}
针对 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()
}
}