
Triggering actions after a time interval with Timers
If you need to trigger an action or send a message after a certain period of time, use the Timer
class, which lets you schedule actions to run in the future, with or without repetition.
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
print("Two seconds have passed!")
}
The type method scheduledTimer(withTimeInterval:repeats:block:)
has the following parameters:
withTimeInterval
: defines the time interval that needs to pass in order for the block closure to be called;repeats
: indicates whether the timer should keep triggering the closure block after it’s activated for the first time;block
: the closure that represents the code to be executed. The closure receives the timer itself as a parameter.
struct ContentView: View {
@State private var message = "Waiting..."
var body: some View {
Text(message)
.font(.title)
.onAppear {
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
message = "Two seconds have passed!"
}
}
}
}
It is possible to use the Timer
inside onAppear(perform:)
, so that when the view appears or updates, an action starts to be processed.
An example of timer usage is creating a counter that follows a specific counting logic and displays all the steps on the screen, such as a countdown sequence.
struct ContentView: View {
@State private var countdown = 5
@State private var timer: Timer?
@State private var action: String = "In process..."
var body: some View {
VStack {
Text("Count: \(countdown)")
.font(.title)
.bold()
Text(action)
.font(.subheadline)
}
.onAppear {
startCountdown()
}
}
func startCountdown() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
print("Count: \(countdown)")
countdown -= 1
if countdown < 1 {
timer?.invalidate()
action = "Action completed!"
}
}
}
}
In the example above, in onAppear(perform:)
the timer starts by calling the startCountdown()
function when the screen is rendered.
Every 1 second, the value of the countdown
variable is decremented. The action
variable shows the current state of the process. We can stop the timer
at any time we want by calling the invalidate()
method, which prevents the timer from firing again.
For recurring timers in SwiftUI that don’t necessitate manual timer invalidation, we can employ the following structure:
let timer = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
It takes advantage of the fact that timers in Swift implement conform with protocols from the Combine framework. The type method publish(every:tolerance:on:in:options:)
returns a publisher that repeatedly emits the current date on the given interval. By calling autoconnect()
, the publisher will automatically connect with the upstream receiver and start publishing values.
What it allows you to do is set up an action to be triggered every time the timer publishes a value using the onReceive(_:perform:)
method.
Here is an example:
struct ContentView: View {
@State private var timePassed = 0
let timer = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
var body: some View {
VStack(spacing: 16) {
Text("Elapsed time:")
.font(.headline)
Text("\(timePassed) seconds")
.font(.title)
.bold()
}
.onReceive(timer) { _ in
timePassed += 1
}
}
}