TimelineView re-evaluates its body on a schedule instead of on data changes. Reach for it when the UI depends on time itself: clocks, timers, and frame-driven animation.
The Context Hands You the Date
The ViewBuilder closure receives a context whose date is the timestamp of the current update. Derive everything you draw from that date.
TimelineView(.animation) { context in
let value = secondsValue(for: context.date)
Circle()
.trim(from: 0, to: value)
.stroke()
}
private func secondsValue(for date: Date) -> Double {
let seconds = Calendar.current.component(.second, from: date)
return Double(seconds) / 60
}
The .animation schedule updates at the platform's animation rate, so the body runs every frame.
Cadence Changes Under You
context.cadence is the current update rate: .live, .seconds, or .minutes. The system can lower it during the view's life, for example when an Apple Watch user drops the wrist. Cadence is Comparable, so branch on it and render a cheaper version at slow cadences.
TimelineView(.animation) { context in
let value = context.cadence <= .live
? nanosValue(for: context.date)
: secondsValue(for: context.date)
Circle().trim(from: 0, to: value).stroke()
}
Built-In Schedules
Besides .animation, SwiftUI ships .everyMinute and .periodic, which fires from a start date at a fixed interval.
TimelineView(.periodic(from: .now, by: 5)) { context in
// body runs every 5 seconds
}
Custom Schedules
Conform to TimelineSchedule and return the update dates from entries(from:mode:). A convenience extension gives it dot syntax at the call site.
final class DailySchedule: TimelineSchedule {
typealias Entries = [Date]
func entries(from startDate: Date, mode: Mode) -> Entries {
(1...30).map { startDate.addingTimeInterval(Double($0 * 24 * 3600)) }
}
}
extension TimelineSchedule where Self == DailySchedule {
static var daily: Self { .init() }
}
TimelineView(.daily) { context in /* ... */ }