В iOS 16 Apple провела масштабную модернизацию Экран Блокировки. Одной из самых ожидаемых функций, которая появилась вместе с обновлением, - виджеты Экрана Блокировки. Как следует из названия, виджеты Экрана Блокировки — это виджеты, отображающие легко просматриваемый контент, который постоянно виден на экране блокировки iPhone и iPad.
Поскольку и виджеты Главного Экрана, и виджеты Экрана Блокировки работают на WidgetKit, способ создания виджета Экран Блокировки очень похож на то, как мы создаем виджеты Главного Экрана. Поэтому в этой статье я не буду показывать вам, как настроить и создать виджет с нуля, как это было описано в моей предыдущей статье.
Вместо этого я сосредоточусь на том, как обновить код существующих виджетов Главного Экрана для поддержки виджетов Экрана Блокировки.
С учетом всего сказанного, давайте начнем!
Краткое Резюме
В демонстрационных целях давайте обновим View Size Widget, который я создал в предыдущей статье. Вкратце напомню, что View Size Widget — это статический виджет Главного Экрана, который отображает размер вью самого виджета. Вот как это выглядит:
Вот полная реализация View Size Widget:
import WidgetKit
import SwiftUI
// MARK: - The Timeline Entry
struct ViewSizeEntry: TimelineEntry {
let date: Date
let providerInfo: String
}
// MARK: - The Widget View
struct ViewSizeWidgetView : View {
let entry: ViewSizeEntry
var body: some View {
GeometryReader { geometry in
VStack {
// Show view size
Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
.font(.system(.title2, weight: .bold))
// Show provider info
Text(entry.providerInfo)
.font(.footnote)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
}
}
}
// MARK: - The Timeline Provider
struct ViewSizeTimelineProvider: TimelineProvider {
typealias Entry = ViewSizeEntry
func placeholder(in context: Context) -> Entry {
// This data will be masked
return ViewSizeEntry(date: Date(), providerInfo: "placeholder")
}
func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) {
let entry = ViewSizeEntry(date: Date(), providerInfo: "snapshot")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let entry = ViewSizeEntry(date: Date(), providerInfo: "timeline")
let timeline = Timeline(entries: [entry], policy: .never)
completion(timeline)
}
}
// MARK: - The Widget Configuration
@main
struct ViewSizeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
provider: ViewSizeTimelineProvider()
) { entry in
ViewSizeWidgetView(entry: entry)
}
.configurationDisplayName("View Size Widget")
.description("This is a demo widget.")
.supportedFamilies([
.systemSmall,
.systemMedium,
.systemLarge,
])
}
}
Если приведенный выше код не имеет для вас никакого смысла, прежде чем продолжить, я настоятельно рекомендую вам сначала прочитать мою статью под названием «Начало работы с WidgetKit».
Добавление Виджетов Экрана Блокировки в Ваши Приложения
Добавление Поддерживаемых Семейств Виджетов
В iOS 16 Apple представила 3 новых семейства виджетов, которые представляют 3 разных типа виджетов Экрана Блокировки, а именно: accessoryCircular, accessoryRectangular и accessorInline.
Давайте продолжим и сделаем эти 3 новых семейства виджетов поддерживаемыми. Это все, что нам нужно сделать, чтобы добавить поддержку виджета Экрана Блокировки в наше существующее расширение виджета.
struct ViewSizeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
provider: ViewSizeTimelineProvider()
) { entry in
ViewSizeWidgetView(entry: entry)
}
.configurationDisplayName("View Size Widget")
.description("This is a demo widget.")
.supportedFamilies([
.systemSmall,
.systemMedium,
.systemLarge,
// Add Support to Lock Screen widgets
.accessoryCircular,
.accessoryRectangular,
.accessoryInline,
])
}
}
Однако, если вы попытаетесь показать виджет на экране блокировки, вы заметите, что UI нашего существующего виджета выглядит не очень в этих новых форм-факторах. Более того, для семейства accessorInline невозможно получить даже размер вью.
Чтобы со всем этим разобраться, нам нужно будет создать 3 отдельных вью SwiftUI для каждого из этих семейств виджетов.
Реализация UI Виджетов Экрана Блокировки
Предположим, что желаемые UI виджетов Экрана Блокировки следующие:
Мы можем реализовать каждый из них так:
/// Widget view for `accessoryInline `
struct InlineWidgetView: View {
var body: some View {
Text("??♂️ View size not available ??♀️")
}
}
/// Widget view for `accessoryRectangular`
struct RectangularWidgetView: View {
var body: some View {
GeometryReader { geometry in
ZStack {
AccessoryWidgetBackground()
.cornerRadius(8)
GeometryReader { geometry in
Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
.font(.headline)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
}
/// Widget view for `accessoryCircular`
struct CircularWidgetView: View {
var body: some View {
GeometryReader { geometry in
ZStack {
AccessoryWidgetBackground()
VStack {
Text("W: \(Int(geometry.size.width))")
.font(.headline)
Text("H: \(Int(geometry.size.height))")
.font(.headline)
}
}
}
}
}
Обратите внимание, что я использую AccessoryWidgetBackground() в качестве фонового вью для RectangularWidgetView и CircularWidgetView. Это SwiftUI-вью со стандартным внешним видом. Мы можем поместить его в ZStack за контентом виджета, чтобы создать виджеты экрана блокировки с непрозрачным фоном.
Примечание: Если вы создаете отдельный файл SwiftUI для каждого вью виджета, обязательно назначьте им таргет расширения виджета. Кроме того, обязательно импортируйте модуль WidgetKit, если вы используете AccessoryWidgetBackground() в качестве фона.
Со всеми этими SwiftUI-вью мы можем вернуться к реализации ViewSizeWidget и соответствующим образом обновить его вью:
struct ViewSizeWidgetView: View {
let entry: ViewSizeEntry
// Obtain the widget family value
@Environment(\.widgetFamily)
var family
var body: some View {
switch family {
case .accessoryRectangular:
RectangularWidgetView()
case .accessoryCircular:
CircularWidgetView()
case .accessoryInline:
InlineWidgetView()
default:
// UI for Home Screen widget
HomeScreenWidgetView(entry: entry)
}
}
}
В приведенном выше коде обратите внимание, как мы получаем значение среды widgetFamily и используем его для условного возврата правильного SwiftUI-вью для каждого семейства виджетов.
Обработка Отсечения Контента
На этом этапе все выглядит нормально при работе на больших устройствах, таких как iPhone 14 Pro Max. Однако, если мы переключимся на устройства с меньшим размером экрана, такие как iPhone 14, вы заметите, что содержимое встроенного виджета обрезается.
Чтобы сделать это, мы можем использовать ViewThatFits для предоставления другого, меньшего вью, когда большее - обрезается. Вот как:
struct InlineWidgetView: View {
var body: some View {
ViewThatFits {
// Provide 2 subviews for `ViewThatFits` evaluation
// Prioritizing from top to bottom
Text("??♂️ View size not available ??♀️")
Text("??♂️ Nope! ??♀️")
}
}
}
ViewThatFits действует как вью, которое будет оценивать свое сабвью в порядке сверху вниз и возвращать первое, которое лучше всего соответствует текущему контексту. Это означает, что в большинстве случаев мы захотим упорядочить самое большое сабвью вверху, за которым следуют другие альтернативные вью, которые меньше.
Параллельное сравнение iPhone 14 и iPhone 14 Pro Max.
Таким образом, мы успешно обновили код наших существующих виджетов Главного Экрана для поддержки виджетов Экрана Блокировки. Браво!
Полный образец кода здесь. Пожалуйста!
Вот и все! Создать виджет Экрана Блокировки на самом деле довольно просто.
Была ли эта статья полезной? Если да, не стесняйтесь ознакомиться с другими моими статьями, связанными с разработкой iOS, здесь. Пожалуйста, следите за мной в Twitter и подписывайтесь на мою рассылку, чтобы не пропустить ни одной из моих будущих статей.
Спасибо за прочтение. ???