Every now and then I need to visualise data in some nice-looking charts. This post shows how to draw charts in a SwiftUI application.

Starting a charting package from scratch was not an option due to time and budget constraints, so I looked for existing solutions.

My vote went to SwiftUI-Charts, which offers nice-looking graphs and easy integration.

Setup and Project Configuration

We start by creating a project in Xcode.

The starting point is the standard SwiftUI application that will be modified.

Next, add the package by opening the project settings.

Press the “plus” button to add a new package. Enter the repository URL: https://github.com/spacenation/swiftui-charts.git.

On the next screen I left the defaults.

In the final screen I also accepted the default values.

Displaying Constant Data

Time to add code. For a first test I used a snippet from the GitHub README and pasted it into ContentView:

import Charts
import SwiftUI
 
struct ContentView: View {
 var body: some View {
  Chart(data: [0.1, 0.3, 0.2, 0.5, 0.4, 0.9, 0.1])
  .chartStyle(
    LineChartStyle(.quadCurve, lineColor:.blue, lineWidth: 5)
   )
 }
}

Running this in the simulator draws a graph of the constant values.

This qualifies as a successful first test.

Displaying Dynamic Data

In my work the data I visualise changes over time (for example, sensor input). Let’s extend the example to update the graph dynamically.

The “sensor” will be a class conforming to ObservableObject that publishes a random Double every 500 milliseconds.

import Foundation
 
class ValuePublisher: ObservableObject {
 @Published var value: Double = 0.0
  
 init() {
  Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
   self.value = Double.random(in: 0...1.0)
  }
 }
}

Instantiate this in ContentView as a @StateObject:

@StateObject var valuePublisher = ValuePublisher()

ValuePublisher emits single values, but we need a list of values. A simple queue structure does the trick.

struct Queue<T> {
 var list = [T]()
  
 mutating func enqueue(_ element: T) {
  list.append(element)
 }
  
 mutating func dequeue() -> T? {
  if!list.isEmpty {
   return list.removeFirst()
  } else {
   return nil
  }
 }
  
 func peek() -> T? {
  if!list.isEmpty {
   return list[0]
  } else {
   return nil
  }
 }
}

Instantiate the queue as a @State variable in ContentView:

@State var doubleQueue = Queue<Double>()

Initialize the underlying list when the view appears:

.onAppear {
 doubleQueue.list = [Double](repeating: 0.0, count: 50)
}

Tell the chart to use the list of values:

Chart(data: doubleQueue.list)

Finally, append published values from ValuePublisher to the queue and remove the oldest value:

.onChange(of: valuePublisher.value) { value in
 self.doubleQueue.enqueue(value)
 _ = self.doubleQueue.dequeue()
}

That’s about it. Here is the full ContentView where I also adjusted the graph appearance:

import Charts
import SwiftUI
 
struct ContentView: View {
  
 @State var doubleQueue = Queue<Double>()
  
 @StateObject var valuePublisher = ValuePublisher()
  
 var body: some View {
  Chart(data: doubleQueue.list)
  .chartStyle(
    AreaChartStyle(.quadCurve, fill:
     LinearGradient(gradient:.init(colors: [Color.blue.opacity(1.0), Color.blue.opacity(0.5)]), startPoint:.top, endPoint:.bottom)
    )
   )
  .onAppear {
    doubleQueue.list = [Double](repeating: 0.0, count: 50)
   }
  .onChange(of: valuePublisher.value) { value in
    self.doubleQueue.enqueue(value)
    _ = self.doubleQueue.dequeue()
   }
  .padding()
 }
}

Screenshot of the final application:

I also uploaded a video showing the values updating dynamically:

https://youtu.be/9cWHGMyeHSc

Feel free to buy me a coffee if you liked this post.

Resources