Пошаговое создание widget в SwiftUI iOS 25.03.2022

Введение

Начиная с iOS 14 был представлен новый фреймворк WidgetKit. С помощью WidgetKit вы можете создавать виджеты и отображать на главном экране (Home screen) чтобы пользователь мог сразу увидеть важную информацию. Виджеты могут быть представлены в разных размерах и размещены на главном экране iOS, в Сегодня (Today) или в Центре уведомлений macOS.

Виджеты, созданные до iOS 14, нельзя разместить на главном экране, но они доступны в Today.

Widget может быть представлен в трех видах: малый, средний и большой. Вы можете поддерживать все три или только один из них.

Widget состоит из следующие элементов

  • Configuration Intent. Пользователь может установить дополнительные параметры для виджета.
  • TimelineProvider. Предоставляет данные во времени и вы можете определить политику обновления.
  • View. Представление SwiftUI для отображения данных. Тут есть предостережение, что не все представления SwiftUI можно использовать в виджетах из-за статического характера.

Добавление новых виджетов в ваше приложение начинается с Widget Extension. Добавьте новую target в проект Xcode. Выполните следующие действия:

  1. Выберите File > New > Target > Widget Extension.
  2. Выберите название продукта (например, WordWidgetExtension). Если вы хотите, чтобы виджет настраивался пользователем, установите "Include Configuration Intent". Не устанавливайте этот флажок для статической конфигурации виджета.
  3. Нажмите Activate в следующем всплывающем окне.

Widget. В приведенном ниже листинге кода вы можете увидеть структуру, которая соответствует протоколу Widget. Это будет основная точка входа для вновь созданной target, и этот код необходим для инициализации и настройки виджета.

import SwiftUI
import WidgetKit

@main
struct WordWidgetExtension: Widget {
    let kind: String = "WordWidgetExtension"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            WordEntryView(entry: entry)
        }
        .configurationDisplayName("Word Widget")
        .description("This is a word widget.")
        .supportedFamilies([.systemMedium])
    }
}

Единственным требованием протокола Widget является свойство body, которое должно возвращать экземпляр WidgetConfiguration. SwiftUI предоставляет две структуры: StaticConfiguration и IntentConfiguration.

StaticConfiguration имеет три параметра для настройки

  • kind. Уникальный строковый идентификатор widget.
  • provider. Объект поставщика данных, предоставляющий массив данных временной шкалы для отображения.
  • content. Замыкание, которое принимает timeline entry и возвращает представление SwiftUI для отображения виджета.

Timeline Entry. Это объект, соответствующий протоколу TimelineEntry. Содержит два свойства: данные и дата и время (когда данные действительны).

struct WordModel {
    let word: String
    let translation: String
    let lang: String
    let example: String

    static func getPlaceholder() -> WordModel {
        return getData().first!
    }

    static func getData() -> [WordModel] {
        return [
            WordModel(word: "comprare", translation: "buy", lang: "it", example: "Io compro una macchina"),
            WordModel(word: "kaufen", translation: "buy", lang: "ge", example: "Ich kaufe ein Auto")
        ]
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let word: WordModel
}

Timeline Provider. Протокол TimelineProvider предоставляет данные во времени с помощью метода getTimeline(in:completion:). В этом методе вы можете определить политику обновления. Кроме того, в этом методе вы создаете массив записей на временной шкале, одну для текущего времени, а другие для будущих, в зависимости от интервала обновления вашего виджета.

В протоколе есть три метода создания данных для записей временной шкалы

  • Метод placeholder() обеспечивает начальный вид и дает пользователю общее представление о внешнем виде виджета.
  • Метод getSnapshot() предоставляет виджету запись для отображения, когда виджет отображается в переходном состоянии.
  • Метод getTimeline() предоставляет виджету массив значений для отображения с течением времени. Вы вызываете completion обработчик с массивом записей временной шкалы и необходимой политикой обновления.

Итак, временная шкала представлена одной или несколькими записями, указанными в методе getTimeline. Есть несколько способов сообщить системе, что нужно инициировать обновление вашей временной шкалы, и это зависит от выбранной политики обновления.

  • atEnd перезагружает временную шкалу после последней даты
  • after(date) перезагружает временную шкалу после указанной даты
  • never WidgetKit никогда не будет запрашивать новую временную шкалу из виджета
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), word: WordModel.getPlaceholder())
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), word: WordModel.getPlaceholder())
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        let results = WordModel.getData()

        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: 5+hourOffset, to: currentDate)!

            let randomIndex = Int.random(in: 0..<results.count)
            let item = results[randomIndex]

            let entry = SimpleEntry(date: entryDate, word: item)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

View.

struct WordEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        ZStack{
            Color.bg
            VStack(spacing: 10) {
                HStack {
                    Text(entry.word.word).font(.headline).foregroundColor(.primaryText)
                    Text(entry.word.lang).font(.footnote).foregroundColor(.primaryText).baselineOffset(6.0)
                }
                Text(entry.word.translation).foregroundColor(.primaryText).font(.body)
                Text(entry.word.example).foregroundColor(.primaryText).font(.caption)
            }
        }
    }
}

import SwiftUI

extension Color {
    static let bg = Color("ColorBg") // #EEE6DB 
    static let primaryText = Color("ColorPrimaryText") // #232220
}
Цитата
Оратор должен исчерпать тему, а не терпение слушателей.
Уинстон Черчилль
Категории
Архив