Probably the best guide to Swift Timer

Swift’s Timer class allows us to schedule work to be repeated or run once. In SwiftUI, we can use the Timer.publish method to create a timer that runs every second and updates a state variable. Once the timer has run 10 times, we cancel it using the timer’s upstream connection.

import SwiftUI

struct TimersSwiftUIPlayground: View {
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @State private var timerRunCount = 0
    
    var body: some View {
        ScrollView {
            Text(String(timerRunCount))
                .onReceive(timer) { _ in
                    timerRunCount += 1
                    
                    guard timerRunCount > 9 else { return }
                    timer.upstream.connect().cancel()
                }
        }
    }
}

Non-repeating Timer

For non-repeating timers, we can use either a closure or a method selector to define the code to be executed. Similarly, we can create repeating timers using the scheduledTimer method and specifying the time interval between each execution.

// Timer with closure
let timer1 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
    print("forceUnwrap.com - have a nice day")
}

// Timer calling method
let timer2 = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: false
)

@objc
func fireTimer() {
    print("forceUnwrap.com - you will learn something new today")
}

Repeating Timer

// Timer with closure
let timer1 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("forceUnwrap.com - You are beautiful")
}

// Timer calling method
let timer2 = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)

@objc
func fireTimer() {
    print("forceUnwrap.com - You will learn something new today")
}

Termination

It’s a good practice to store timers in properties so we can terminate them when needed. We can do this by calling the timer’s invalidate method or setting its value to nil. Additionally, we can use the tolerance property to optimize for power savings and responsiveness.

// Timer with closure
var timerRunCount = 0

Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("forceUnwrap.com -> You can do this ")
    timerRunCount += 1
    
    guard timerRunCount == 5 else { return }
    timer.invalidate()
}

// Timer calling method
var timer: Timer?
var timerRunCount = 0

let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(invalidateTimerIfNeeded),
    userInfo: nil,
    repeats: true
)

@objc
private func invalidateTimerIfNeeded() {
    print("forceUnwrap.com -> You can do this ")
    timerRunCount += 1
    
    guard timerRunCount == 5 else { return }
    timer?.invalidate()
}

Tolerance

Tolerance helps the system to optimize for increased power savings and responsiveness. Apple documentation.

let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)
timer.tolerance = 0.3

@objc
private func fireTimer() {
    print("forceUnwrap.com")
}

Run loops

When creating timers on the main thread, we may encounter issues with user interaction and UI updates. To avoid these problems, we can use the RunLoop.current method to add the timer to the main thread’s run loop.

Timer issue with user interaction
let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)
RunLoop.current.add(timer, forMode: .common)

@objc
private func fireTimer() {
    print("forceUnwrap.com")
}
Timer solution

Sync with screen update

Alternatively, we can sync the timer with the display refresh rate using the CADisplayLink class.

let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: .current, forMode: .default)

@objc
private func update() {
    print("forceUnwrap.com")
}

Overall, Timer is a useful class for scheduling and executing code on a periodic basis in Swift.

Leave a Comment