Skip to Content
Course content

220: Real-Time Data Updates in Swift Charts

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start pumping live data into a Swift Chart, you'll likely fall into a trap I did early on: thinking that simply appending a new value to your data array will automatically make the chart "slide" or scroll. You'll write your append() logic, run the app, and realize that instead of a smooth scrolling timeline, your chart is just getting more and more crowded as the X-axis compresses to fit every single point since the app started. It doesn't "move"; it just squishes.

The "Append and Pray" Approach vs. The Sliding Window

The misconception is that Chart handles the temporal windowing for you. It doesn't. It renders the data you give it. If you give it 10 points, it shows 10. If you give it 10,000 points over an hour, it tries to render 10,000 points in the same amount of screen real estate. Not only does this look terrible, but your frame rate will tank as the data set grows.

To get that professional "heart rate monitor" or "CPU dashboard" feel, you need a sliding window. This means you maintain a fixed number of samples. When a new piece of data arrives, you add it to the end and drop the oldest piece from the front. I usually recommend a Deque for this if you're doing heavy lifting, but for most Swift Charts use cases, a simple Array with dropFirst() works perfectly.

struct CPUUsageView: View {
    @State private var usageData: [CPUReading] = []
    let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()

    var body: some View {
        Chart(usageData) { reading in
            LineMark(
                x: .value("Time", reading.timestamp),
                y: .value("Usage", reading.value)
            )
        }
        .animation(.easeInOut, value: usageData) // The magic sauce
        .onReceive(timer) { _ in
            let newReading = CPUReading(timestamp: Date(), value: Double.random(in: 10...90))
            
            // Maintain a window of 20 points
            usageData.append(newReading)
            if usageData.count > 20 {
                usageData.removeFirst()
            }
        }
    }
}

Triggering Animations Without Resetting the View

Now, if you run the code above, you'll notice the line jumps. It's not "sliding"; it's teleporting. This is where most people get frustrated. They try to wrap the whole chart in a withAnimation block, but that often causes the entire chart to fade in and out or glitch.

The secret is the .animation(_:value:) modifier. By attaching the animation to the usageData value specifically, you're telling SwiftUI: "Whenever the identity or content of this array changes, interpolate the positions of the marks." Because we are keeping the number of points constant (via our sliding window), SwiftUI can map the old point at index 0 to the new point at index 0, creating that fluid, shifting motion we're looking for.

One pro tip: make sure your data model conforms to Identifiable using a unique ID (like a UUID or the timestamp itself). If SwiftUI can't uniquely identify which point is which, it will give up on the animation and just "snap" the line to the new position. I've spent way too many hours debugging "jittery" charts only to realize I was using the index as the ID, which changes every time I removeFirst().




📋 Practical Task

Build a Real-Time Network Latency Monitor

Your goal is to create a view that simulates a network ping monitor. The chart should show the latency (in milliseconds) of a connection over time.

  • Create a LatencyReading struct that conforms to Identifiable.
  • Implement a timer that generates a random latency value between 20ms and 150ms every 0.3 seconds.
  • Implement a sliding window that keeps exactly 30 data points on the screen at all times.
  • Use a LineMark to visualize the data.
  • Apply a .animation modifier so that the line slides smoothly to the left as new data arrives.
  • Bonus: Add a RuleMark at 100ms to indicate a "high latency" threshold.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.