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

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

TimelineView был представлен в iOS 15 и позволяет вам планировать и управлять обновлениями ваших представлений через регулярные промежутки времени. Он предоставляет удобный способ обработки динамического содержимого и данных в реальном времени за счет автоматического обновления представлений на основе предварительно заданного расписания.

TimelineView действует как контейнер, в который вы можете встроить свой контент.

var body: some View {
    TimelineView(.everyMinute) { context in
        // Your Content
    }
}

Типы расписания

SwiftUI предоставляет несколько встроенных типов расписаний:

  • .everyMinute — для обновления представления временной шкалы в начале каждой минуты.
  • .periodic(from:by:) — для регулярного обновления представления временной шкалы. Принимает дату начала и интервал, который определяет частоту обновлений.
let interval: TimeInterval = 5 // 5 seconds
TimelineView(.periodic(from: .now, by: interval)) { context in
    // Your Content
}
  • .explicit(_:) — для обновления представления временной шкалы в определенные моменты времени. Принимает последовательность из Date объектов.
let dates = [
    Date(timeIntervalSinceNow: 5 * 60),     // in 5 min
    Date(timeIntervalSinceNow: 15 * 60),    // in 15 min
    Date(timeIntervalSinceNow: 30 * 60)     // in 30 min
]

TimelineView(.explicit(dates)) { context in
    // Your Content
}

Если встроенных типов расписаний вам недостаточно, то вы можете определить свои собственные, создав тип, соответствующий протоколу TimelineSchedule.

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

Контекст

Замыкание, создающее содержимое, получает входные данные типа TimelineView.Context, которые можно использовать для настройки внешнего вида содержимого.

Объект контекста имеет 2 свойства:

  • date — дата запуска обновления.
  • cadence — скорость, с которой представления временной шкалы могут получать обновления.

Каденс

Cadence — это перечисление со следующими случаями:

  • .live — постоянно обновляет вид.
  • .seconds — обновляет представление примерно раз в секунду.
  • .minutes — обновляет вид примерно раз в минуту.

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

let showMilliseconds = context.cadence == .live
let showSeconds = context.cadence <= .seconds

Пример проекта

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

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

enum USATimeZone: String, CaseIterable {
    case EDT, CDT, MDT, PDT, AKDT, HST

    var timeZone: TimeZone {
        TimeZone(abbreviation: rawValue)!
    }
}

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

struct TimeRow: View {
    let timeZone: String
    let time: String

    var body: some View {
        HStack {
            Text(timeZone)
                .font(.body)

            Spacer(minLength: 16)

            Text(time)
                .font(.headline)
        }
    }
}

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

struct TimeZonesView: View {
    let timeZones: [TimeZone] = {
        USATimeZone.allCases
            .map(\.timeZone)
            .sorted {
                $0.secondsFromGMT() > $1.secondsFromGMT()
            }
    }()

    var body: some View {
        NavigationStack {
            TimelineView(.periodic(from: .now, by: 1)) { context in
                List(timeZones, id: \.identifier) { timeZone in
                    let time = formattedTime(context.date,
                                             timeZone: timeZone)
                    TimeRow(timeZone: formattedTimeZone(timeZone),
                            time: time)
                }
            }
            .navigationTitle(Text("Time in the USA"))
        }
    }

    private static let timeFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .none
        formatter.timeStyle = .short
        return formatter
    }()

    private func formattedTime(_ date: Date,
                               timeZone: TimeZone) -> String {
        let formatter = Self.timeFormatter
        formatter.timeZone = timeZone
        return formatter.string(from: date)
    }

    private func formattedTimeZone(_ timeZone: TimeZone) -> String {
        let name = timeZone.localizedName(for: .generic, locale: .current)
        let abbreviation = timeZone.abbreviation()

        guard let name, let abbreviation else {
            return ""
        }

        return "\(name) (\(abbreviation))"
    }
}

Надеюсь, вам понравилась статья. Спасибо за прочтение! 🙂