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
    ...
}

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.

Ace The iOS Interview
The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I’ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I’ve mentored junior engineers, bui…
Indie Watch
Indie Watch is an exclusive weekly hand-curated newsletter showcasing the best iOS, macOS, watchOS, and tvOS apps from developers worldwide.
Universal Link & Apple App Site Association Testing Tool
Easily verify and test Universal GetUniversal.link is a free tool for verifying and testing Apple App Site Association (AASA) files. Ensure your Universal Links are configured correctly with easy link creation, real-time testing, and team collaboration features. Save the website as a bookmark for quick access on devices and simulators. Simplify your AASA file troubleshooting today!