cancellable timer
Last updated
Was this helpful?
Last updated
Was this helpful?
📗 參考:Paul ⟩ Counting down with a Timer
👥 相關:.onChange(), .onRecieve() events.
import SwiftUI
struct TimerView: View {
// gives the user 100 seconds to start with
@State private var timeRemaining = 100
@State private var isTimerStopped = false
@State private var isTimerCancelled = false
// ⭐️ timer that fires once a second on the main thread.
let timer = Timer
// ⭐️ Timer.TimerPublisher
.publish(
every: 1, // timer fires every 1 second.
on: .main, // run on the .main thread (UI thread).
in: .common // run on the .common run loop.
// Run loop lets iOS handle running code while the user is actively
// doing something, such as scrolling in a list
)
// ⭐️ connect automatically when a subscriber attaches
.autoconnect()
// ⭐️ detect whether app has gone background
@Environment(\.scenePhase) private var scenePhase
@State private var isActive = true
var body: some View {
VStack {
timerView
HStack {
stopButton
cancelButton
}
}
.padding(8)
.border(.secondary)
}
/// text for timer
var text: some View {
Text("Time: \(timeRemaining)")
.font(.largeTitle)
.foregroundColor(isTimerCancelled ? .secondary : .white)
.padding(.horizontal, 20)
.padding(.vertical, 5)
.background(.pink.opacity(0.75))
.clipShape(Capsule())
}
/// timer view
var timerView: some View {
text
// ⭐️ connect `timer` automatically❓
.onReceive(timer) { time in
// ⭐️ if app goes background or timer stopped, stop count-down immediately.
guard isActive && !isTimerStopped else { return }
// ⭐️ count down
if timeRemaining > 0 { timeRemaining -= 1 }
}
// ⭐️ mark the app inactive once it goes background.
.onChange(of: scenePhase) { newPhase in
isActive = (newPhase == .active)
}
}
/// Stop/Resume button
var stopButton: some View {
Button {
isTimerStopped.toggle()
} label: {
Text(isTimerStopped ? "Resume" : "Stop")
}
.disabled(isTimerCancelled)
}
/// cancel button
var cancelButton: some View {
Button {
// ⭐️ cancel the timer
timer // Publishers.Autoconnect<Timer.TimerPublisher>
.upstream // Timer.TimerPublisher (ConnectablePublisher)
.connect() // allow to produce elements, return an instance (Cancellable)
.cancel() // ⭐️
isTimerCancelled = true
} label: {
Text("Cancel")
}
.disabled(isTimerCancelled)
}
}
Ray ⟩ Combine: Asynchronous Programming with Swift, Ch. 11: Timers
Foundation ⟩ Task Management ⟩ Timer (class)
Timer.TimerPublisher (class)
.autoconnect() -> Publishers.Autoconnect<Timer.TimerPublisher>
Combine ⟩ ConnectablePublisher (protocol) ⟩
.connect() ⭐️ - connects to the publisher, allowing it to produce elements, and returns an instance with which to cancel publishing.
SwiftUI ⟩
Scenes ⟩ ScenePhase (enum)
State ⟩ EnvironmentValues (struct) ⟩ .scenePhase (instance property)
View ⟩ Input and Event Modifiers ⟩
.onReceive(_:perform:) - perform action when emitted data detected.
.onChange(of:perform:) - perform action when specific value changes.
use @Environment values.
use Timer.TimerPublisher (ConnectablePublisher
) Publisher.