Reading time: 1 min

This recipe shows how to implement a timer in SwiftUI in order to update the UI state at a specific interval.

There are two ways of going about it:

  1. The simple one, with onReceive.
  2. A bit more complex, but also more powerful one, using SimpleTimer wrapper.

Direct updates with onReceive

Use onReceive to trigger an event whenever the timer Publisher emits:

import Combine
@State private var elapsedTime = 0
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var body: some View {
    VStack {
        Text("Elapsed time: \(elapsedTime) s")
    }.onReceive(timer) { _ in
        self.elapsedTime += 1

The SimpleTimer wrapper

Start off with this simple wrapper that wraps a TimerPublisher:

import Combine

class SimpleTimer {
  // the interval at which the timer ticks
  let interval: TimeInterval
  // the action to take when the timer ticks
  let onTick: () -> Void

  private var timer: Publishers.Autoconnect<Timer.TimerPublisher>? = nil
  private var subscription: AnyCancellable? = nil

  init(interval: TimeInterval, onTick: @escaping () -> Void) {
    self.interval = interval
    self.onTick = onTick

  var isRunning: Bool {
    timer != nil

  // start the timer and begin ticking
  func start() {
    timer = Timer.publish(every: interval, on: .main, in: .common).autoconnect()
    subscription = timer?.sink(receiveValue: { _ in

  // cancel the timer and clean up its resources
  func cancel() {
    timer = nil
    subscription = nil

Here's an example where the timer ticks every second:

@State private var remainingTime = 0
timer = SimpleTimer(interval: 1) {
  self.remainingTime -= 1

Then, simply start it when needed:


And after you don't need it anymore, call cancel:


Tip: TimerPublisher emits the current date with every tick, and you can optionally consume that value as the parameter in onReceive and sink(receiveValue: blocks.

Next Post Previous Post