Введение
Начиная с iOS 14 был представлен новый фреймворк WidgetKit. С помощью WidgetKit вы можете создавать виджеты и отображать на главном экране (Home screen) чтобы пользователь мог сразу увидеть важную информацию. Виджеты могут быть представлены в разных размерах и размещены на главном экране iOS, в Сегодня (Today) или в Центре уведомлений macOS.
Виджеты, созданные до iOS 14, нельзя разместить на главном экране, но они доступны в Today.
Widget может быть представлен в трех видах: малый, средний и большой. Вы можете поддерживать все три или только один из них.
Widget состоит из следующие элементов
Добавление новых виджетов в ваше приложение начинается с Widget Extension. Добавьте новую target в проект Xcode. Выполните следующие действия:
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 }