Time-Based View Updates in SwiftUI
SwiftUI's TimelineView
is a powerful feature for building views that update according to whatever schedule you provide.
Whether you're creating a real-time clock, a countdown timer, or periodic data visualizations, TimelineView
simplifies the process by letting you schedule updates with fine-grained control.
What is TimelineView?
A TimelineView
is simply a container view in SwiftUI that redraws its contents at scheduled points in time. It's especially useful for time-sensitive or periodic tasks where precision is important, such as:
- Clocks or calendars.
- Periodic updates (e.g., refreshing live data every 10 seconds).
- Animations or visualizations tied to time.
As a container view, it has no appearance of it's own, it simply accepts a closure that creates the content you wish to redraw:
TimelineView(...) { _ in
// Some view
}
The TimelineView
initializer accepts a TimelineSchedule
to control when updates happen and there's a few different types of schedules you can pick from:
.periodic(from:by:)
Regularly refresh the view at defined intervals (e.g. every second), perfect for periodic updates like clocks and timers.
struct RealTimeClockView: View {
var body: some View {
// Redraws the view every 1 second
TimelineView(.periodic(from: Date(), by: 1)) { context in
let currentDate = context.date
let formattedTime = timeFormatter.string(from: currentDate)
VStack {
Text("Current Time")
.font(.headline)
Text(formattedTime)
.font(.largeTitle)
.bold()
}
.padding()
}
}
private var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm:ss"
return formatter
}
}
.explicit(dates:)
Refresh the view at predefined moments (i.e. alarms / reminders) using a specific list of dates.
struct ScheduledUpdatesView: View {
let updateTimes = [
Calendar.current.date(bySettingHour: 9, minute: 0, second: 0, of: Date())!,
Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: Date())!,
Calendar.current.date(bySettingHour: 18, minute: 0, second: 0, of: Date())!
]
var body: some View {
TimelineView(.explicit(updateTimes)) { context in
VStack {
Text("Next Update")
.font(.headline)
Text("Updated at \(context.date, style: .time)")
.font(.largeTitle)
}
.padding()
}
}
}
.animation(minimumInterval:paused:)
Drive smooth, time-based animations with precise timing.
struct RotatingClockView: View {
var body: some View {
// 60 FPS
TimelineView(.animation(minimumInterval: 1 / 60)) { context in
let seconds = Calendar.current.component(.second, from: context.date)
VStack {
Text("Clock Animation")
.font(.headline)
ZStack {
Circle()
.stroke(lineWidth: 2)
.frame(width: 150, height: 150)
Rectangle()
.fill(Color.red)
.frame(width: 2, height: 75)
.offset(y: -37.5)
.rotationEffect(.degrees(Double(seconds) * 6)) // Rotate based on seconds
}
}
.padding()
}
}
}
.everyMinute
Refreshes the view at the beginning of each minute.
TimelineView(.everyMinute) { context in
...
}
Subscribe
New articles straight to your inbox.
No spam. Unsubscribe anytime.
Custom Schedules
You can create your own custom schedule by subclassing TimelineSchedule
:
struct CustomSchedule: TimelineSchedule {
func entries(from startDate: Date, mode: TimelineScheduleMode) -> [Date] {
var entries: [Date] = []
...
entries.append(someDateObject)
return entries
}
}
For a schedule containing only dates in the past, the TimelineView
shows the last date in the schedule. For a schedule containing only dates in the future, the TimelineView
draws its content using the current date until the first scheduled date arrives.
Limitations of TimelineView
While TimelineView
is great for time-driven updates, it has some limitations:
- Not event-driven: It can’t react to data updates outside of its time schedule (e.g., WebSocket or user input).
- System-managed scheduling: SwiftUI may coalesce updates to conserve resources, so it’s not guaranteed for extremely high-precision tasks like millisecond timing.
- Static timing schedules: Once a
TimelineView
is created, its timing schedule cannot be dynamically modified.
For event-driven use cases (like a real-time data stream), consider using Observation
or Combine
instead.
In case you missed it, here's a recording of my talk at SwiftCraft earlier this year:
If you're interested in more articles about iOS Development & Swift, check out my YouTube channel or follow me on Twitter.
And, if you're an indie iOS developer, make sure to check out my newsletter! Each issue features a new indie developer, so feel free to submit your iOS apps.
Subscribe
New articles straight to your inbox.
No spam. Unsubscribe anytime.