Разработка
Изучаем WidgetKit: пишем виджет для Control Center в iOS 18
Иногда мне хотелось обновить виджеты фонарика и камеры на экране блокировки. Похоже, этот день настал. В iOS 18 мы можем сделать это благодаря виджетам управления (Control Widgets).
Я уже играл с виджетами, и когда виджеты управления были анонсированы на WWDC 2024, сессия, связанная с ними, была первой, которую я посмотрел, когда летел домой из SFO.
Архитектура настолько похожа на другие виджеты, что я за пятнадцать минут создал простой элемент управления для своего приложения Fusion, чтобы напрямую воспроизводить станцию в Apple Music.
В этой статье мы поговорим о контролах и о том, как начать работу с ними!
Что такое виджеты управления?
Виджеты управления появились в iOS 18 и iPadOS. Они позволяют нам добавлять быстрые действия из приложения в такие места, как Центр управления или нижние виджеты экрана блокировки. Это ярлыки для простых задач, таких как включение фонарика телефона, или действий вроде записи экрана.
Протокол ControlWidget
Начнем с протокола ControlWidget
. Он похож на протокол виджета, с которым мы привыкли работать:
@available(iOS 18.0, *)
@MainActor protocol ControlWidget {
@MainActor @preconcurrency init()
associatedtype Body : ControlWidgetConfiguration
@ControlWidgetConfigurationBuilder @MainActor @preconcurrency var body: Self.Body { get }
}
- У него есть свойство
body
. Здесь мы описываем, как выглядит и что делает виджет. - Сам
body
возвращает конфигурациюControlWidgetConfiguration
.
ControlWidgetConfiguration
ControlWidgetConfiguration
определяет тело элемента управления и настраивает его.
xxxxxxxxxx
@available(iOS 18.0, *)
@MainActor public protocol ControlWidgetConfiguration {
associatedtype Body : ControlWidgetConfiguration
@ControlWidgetConfigurationBuilder @MainActor @preconcurrency var body: Self.Body { get }
}
Есть несколько вещей, которые мы можем с ним сделать:
- Задать описание для виджета
- Дать виджету отображаемое имя
- Попросить пользователя установить его сразу
- Обработка push-уведомлений
ControlWidgetButton
ControlWidgetButton
используется для создания кнопок в виджетах управления. Он предназначен для простых, немедленных действий и принимает внешний вид системы для согласованности.
xxxxxxxxxx
@MainActor struct ControlWidgetButton<Label, ActionLabel>: ControlWidgetTemplate where Label: View, ActionLabel: View {
@MainActor var body: some ControlWidgetTemplate { get }
typealias Body = some ControlWidgetTemplate
}
extension ControlWidgetButton {
@MainActor init<Action>(action: Action, @ViewBuilder label: @escaping () -> Label, @ViewBuilder actionLabel: @escaping (Bool) -> ActionLabel) where Action: AppIntent
}
Мы можем использовать его так:
xxxxxxxxxx
ControlWidgetButton(action: SomeIntent()) {
Label("Button Text", systemImage: "symbol.name")
}
Создание первого виджета управления
Давайте рассмотрим создание простого виджета управления с помощью ControlWidgetButton
. В этом примере мы создадим кнопку для воспроизведения станции в Apple Music.
xxxxxxxxxx
#if swift(>=6.0)
@available(iOS 18, *)
struct PlayDiscoveryStation: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "com.rudrankriyam.fussion") {
ControlWidgetButton(action: PlayDiscoveryStationIntent()) {
Label("Play Discovery Station", systemImage: "radio.fill")
}
}
}
}
#endif
В этом коде:
- Мы определяем структуру
PlayDiscoveryStation
, соответствующуюControlWidget
. - Мы используем
StaticControlConfiguration
для настройки нашего виджета. ControlWidgetButton
создает кнопку с меткой и связанным с ней действием.
Обработка действия
Действие определяется в отдельном AppIntent
:
xxxxxxxxxx
import MusadoraKit
@available(iOS 18, *)
struct PlayDiscoveryStationIntent: AppIntent {
static var title: LocalizedStringResource = "Play Apple Music Discovery Station"
static var description = IntentDescription("Plays the Apple Music Discovery station in the control center.")
init() {
}
func perform() async throws -> some IntentResult {
try await WidgetMusicPlayer.playDiscoveryStation()
return .result()
}
}
Это намерение определяет, что происходит при нажатии на кнопку — в данном случае воспроизведение станции discovery с помощью класса WidgetMusicPlayer
.
Функция perform()
помечена как async
, что позволяет ей ждать начала воспроизведения без блокировки. После начала воспроизведения она просто возвращает .result()
, не возвращая ничего, так как действие запуска воспроизведения является предполагаемым результатом.
Интеграция виджетов управления с существующими пакетами виджетов
Если у нас уже есть WidgetBundle
для приложения, мы обновим его, чтобы включить новые виджеты панели управления для iOS 18:
xxxxxxxxxx
struct FussionWidgetsBundle: WidgetBundle {
var body: some Widget {
DiscoveryStationWidgets()
PersonalStationWidgets()
SongStationWidget()
if #available(iOSApplicationExtension 18, *) {
PlayDiscoveryStation()
}
}
}
Это позволяет приложению поддерживать как старые версии iOS с существующими виджетами, так и iOS 18 с новыми управляющими виджетами.
Вот как выглядит Control Widget в действии в Центре управления:
А вот как установить его на экран блокировки:
Что дальше
Когда мы освоим базовые виджеты, мы сможем изучить больше возможностей новых API. У нас есть ControlWidgetToggle
для функций, которые имеют четкое состояние включения/выключения, или ControlValueProvider
для актуальной информации.
Счастливого виджетирования!
-
Новости1 неделя назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков3 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.12
-
Разработка3 недели назад
«Давайте просто…»: системные идеи, которые звучат хорошо, но почти никогда не работают