โจcancellable timer
Last updated
Last updated
๐ ๅ่๏ผ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.