Plotting in SwiftUI

Translation prepared as part of the course “iOS Developer. Basic”… If you are interested in learning more about the course, come to Open Day online.

From time to time, I need to visualize data in the form of beautiful graphs. This article will show you how to draw graphs in a SwiftUI application.

Building a graphing package from scratch was not possible due to time and budget constraints, so I had to look for existing solutions.

The choice fell on SwifUI-Chartswhich offers really nice looking graphics and easy integration.

Installation and configuration of the project

First, we’ll start by creating a project in Xcode.

The starting point is a standard SwiftUI application that will be modified to display the graph.

In the next step, the package will be added by opening the project settings.

By clicking on the plus button, you can add a new package. Here you need to specify the full path to the repository: https://github.com/spacenation/swiftui-charts.git

On the next screen, I left all the default values.

On the last screen, I also left the defaults as they were.

Displaying persistent data

It’s time to add some code. For the first test, I just took some code snippets from the Github-Readme and added them to 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 will draw a nice graph showing constant values.

This is considered the first successful test.

Displaying dynamic data

In the field I work in, the data I need to render changes over time, for example as a result of touch input. Let’s expand the example to dynamically update the graphs in response to data changes.

The class will be used as a “sensor” ObservableObjectwhich just posts a random value 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)
        }
    }
}

It needs to be instantiated in ContentView as a variable @State

@StateObject var valuePublisher = ValuePublisher()

ValuePublisher produces only individual values, but we need these values ​​to be available as a list. This task is handled by a simple data structure queue.

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

This queue will be instantiated as a variable @State in ContentView

@State var doubleQueue = Queue<Double>()

The main list must be initialized when the view appears.

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

The graph should also contain information about the list in which the values ​​are stored.

Chart(data: doubleQueue.list)

In the last step, the published values ValuePublisher must be added to the queue, and the oldest value from the queue must be removed.

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

That’s all, here’s the complete ContentViewwhere I also slightly changed the appearance of the graph.

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()
    }
}

Here is a screenshot of the final app

I also uploaded a video so you can see what it looks like when the values ​​are updated dynamically.

Video: SwiftUI charts

Resources

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *