Перевод подготовлен в рамках курса "iOS Developer. Basic". Если вам интересно узнать о курсе больше, приходите на день открытых дверей онлайн.

Время от времени мне нужно визуализировать данные в виде красивых графиков. В этой статье будет показано, как рисовать графики в SwiftUI-приложении.

Создание пакета для построения графиков с нуля было невозможным из-за ограничений по времени и бюджету, поэтому мне пришлось искать уже существующие решения.

Выбор пал на SwifUI-Charts, который предлагает действительно красиво выглядящие графики и простую интеграцию.

Установка и конфигурация проекта

Сначала мы начнем с создания проекта в XCode.

Отправной точкой является стандартное SwiftUI-приложение, которое будет модифицировано для отображения графика.

На следующем этапе пакет будет добавлен, за счет открытия настройки проекта.

Нажав на кнопку "плюс", можно добавить новый пакет. Здесь необходимо указать полный путь к репозиторию: https://github.com/spacenation/swiftui-charts.git.

На следующем экране я оставил все значения по умолчанию.

На последнем экране я также оставил значения по умолчанию такими, какими они были.

Отображение постоянных данных

Пришло время добавить код. Для первого теста я просто взял несколько фрагментов кода из Github-Readme и добавил их в 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)
            )
    }
}

При запуске этого в симуляторе будет нарисован красивый график, показывающий постоянные значения.

Это считается первым успешным тестом.

Отображение динамических данных

В той области, в которой я работаю, данные, которые мне нужно визуализировать, меняются со временем, например, в результате сенсорного ввода. Давайте расширим пример, чтобы динамически обновлять графики в соответствии с изменением данных.

В качестве "сенсора" будет использоваться класс ObservableObject, который просто публикует случайное значение Double каждые 500 миллисекунд.

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

Его нужно инстанцировать в ContentView как переменную @State.

@StateObject var valuePublisher = ValuePublisher()

ValuePublisher выдает только отдельные значения, но нам необходимо, чтобы эти значения были доступны в виде списка. С этой задачей справляется простая очередь структуры данных.

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

Эта очередь будет инстанцирована как переменная @State в ContentView

@State var doubleQueue = Queue<Double>()

Основной список должен быть инициализирован при появлении представления.

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

График также должен содержать информацию о списке, в котором хранятся значения.

Chart(data: doubleQueue.list)

На последнем этапе опубликованные значения ValuePublisher должны быть добавлены в очередь, а самое старое значение из очереди должно быть удалено.

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

На этом все, вот полный ContentView, где я также немного изменил внешний вид графика.

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

Вот скриншот окончательного варианта приложения

Я также загрузил видео, чтобы вы могли посмотреть, как это выглядит, когда значения обновляются динамически.

Видео: Графики в SwiftUI

Ресурсы