SwiftUI: How to Implement Scheduled View Updates

Mykhailo Moiseienko
4 min readJun 14, 2023

Developers often face the need to update views automatically according to a schedule, especially when dealing with real-time data such as the current time, stock prices, or weather updates. In such cases, ensuring that views are consistently refreshed becomes essential to provide users with the most up-to-date information.

In this article, we’ll explore how to effectively utilize TimelineView to achieve seamless and timely updates in SwiftUI views.

TimelineView was introduced in iOS 15 and allows you to schedule and manage updates to your views at regular intervals. It provides a convenient way to handle dynamic content and real-time data by automatically refreshing the views based on a predefined schedule.

A TimelineView acts as a container into which you can embed your content.

var body: some View {
TimelineView(.everyMinute) { context in
// Your Content
}
}

Schedule Types

SwiftUI provides several built-in schedule types to use:

  • .everyMinute — for updating a timeline view at the start of every minute.
  • .periodic(from:by:) — for updating a timeline view at regular intervals. Accepts start date and interval which defines the frequency of updates.
let interval: TimeInterval = 5 // 5 seconds
TimelineView(.periodic(from: .now, by: interval)) { context in
// Your Content
}
  • .explicit(_:) — for updating a timeline view at explicit points in time. Accepts a sequence of Date objects.
let dates = [
Date(timeIntervalSinceNow: 5 * 60), // in 5 min
Date(timeIntervalSinceNow: 15 * 60), // in 15 min
Date(timeIntervalSinceNow: 30 * 60) // in 30 min
]

TimelineView(.explicit(dates)) { context in
// Your Content
}

If built-in schedule types are not enough for you, then you can define your own by creating a type that conforms to the TimelineSchedule protocol.

For a schedule containing only dates in the past, the timeline view shows the last date in the schedule. For a schedule containing only dates in the future, the timeline draws its content using the current date until the first scheduled date arrives.

Context

The closure that creates the content receives an input of type TimelineView.Context that you can use to customize the content’s appearance.

The context object has 2 properties:

  • date — a date that triggered the update.
  • cadence — a rate at which timeline views can receive updates.

Cadence

Cadence is an enum with the following cases:

  • .live — updates the view continuously.
  • .seconds — updates the view approximately once per second.
  • .minutes — updates the view approximately once per minute.

You can use cadence to hide information that updates faster than the view’s current update rate. For example, you can decide whether to show seconds or milliseconds.

let showMilliseconds = context.cadence == .live
let showSeconds = context.cadence <= .seconds

Sample Project

Now we’ll explore a sample project that demonstrates how to use TimelineView to display time in several time zones which will be updated automatically.

Sample Project Demo

First, we need to prepare a list of time zones that we want to display. In that sample, we’re going to use several time zones available in the USA.

enum USATimeZone: String, CaseIterable {
case EDT, CDT, MDT, PDT, AKDT, HST

var timeZone: TimeZone {
TimeZone(abbreviation: rawValue)!
}
}

Then, we need to implement a view that will be used to display time in a specific time zone.

struct TimeRow: View {
let timeZone: String
let time: String

var body: some View {
HStack {
Text(timeZone)
.font(.body)

Spacer(minLength: 16)

Text(time)
.font(.headline.monospacedDigit())
}
}
}

Finally, we’ll add a view that will display TimelineView with a list of time zones.

struct TimeZonesView: View {
let timeZones: [TimeZone] = {
USATimeZone.allCases
.map(\.timeZone)
.sorted {
$0.secondsFromGMT() > $1.secondsFromGMT()
}
}()

var body: some View {
NavigationStack {
TimelineView(.periodic(from: .now, by: 1)) { context in
List(timeZones, id: \.identifier) { timeZone in
let time = formattedTime(context.date,
timeZone: timeZone)
TimeRow(timeZone: formattedTimeZone(timeZone),
time: time)
}
}
.navigationTitle(Text("Time in the USA"))
}
}

private static let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .medium
return formatter
}()

private func formattedTime(_ date: Date,
timeZone: TimeZone) -> String {
let formatter = Self.timeFormatter
formatter.timeZone = timeZone
return formatter.string(from: date)
}

private func formattedTimeZone(_ timeZone: TimeZone) -> String {
let name = timeZone.localizedName(for: .generic, locale: .current)
let abbreviation = timeZone.abbreviation()

guard let name, let abbreviation else {
return ""
}

return "\(name) (\(abbreviation))"
}
}

I hope you enjoyed the article. Thanks for reading! 🙂

--

--

Mykhailo Moiseienko

📱 Senior iOS Software Engineer. ✍️ Writing about iOS Development, Swift, and SwiftUI.