Введение
Начиная с 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
}