Иногда мне хотелось обновить виджеты фонарика и камеры на экране блокировки. Похоже, этот день настал. В 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
определяет тело элемента управления и настраивает его.
@available(iOS 18.0, *) @MainActor public protocol ControlWidgetConfiguration { associatedtype Body : ControlWidgetConfiguration @ControlWidgetConfigurationBuilder @MainActor @preconcurrency var body: Self.Body { get } }
Есть несколько вещей, которые мы можем с ним сделать:
- Задать описание для виджета
- Дать виджету отображаемое имя
- Попросить пользователя установить его сразу
- Обработка push-уведомлений
ControlWidgetButton
ControlWidgetButton
используется для создания кнопок в виджетах управления. Он предназначен для простых, немедленных действий и принимает внешний вид системы для согласованности.
@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 }
Мы можем использовать его так:
ControlWidgetButton(action: SomeIntent()) { Label("Button Text", systemImage: "symbol.name") }
Создание первого виджета управления
Давайте рассмотрим создание простого виджета управления с помощью ControlWidgetButton
. В этом примере мы создадим кнопку для воспроизведения станции в Apple Music.
#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
:
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:
struct FussionWidgetsBundle: WidgetBundle { var body: some Widget { DiscoveryStationWidgets() PersonalStationWidgets() SongStationWidget() if #available(iOSApplicationExtension 18, *) { PlayDiscoveryStation() } } }
Это позволяет приложению поддерживать как старые версии iOS с существующими виджетами, так и iOS 18 с новыми управляющими виджетами.
Вот как выглядит Control Widget в действии в Центре управления:
А вот как установить его на экран блокировки:
Что дальше
Когда мы освоим базовые виджеты, мы сможем изучить больше возможностей новых API. У нас есть ControlWidgetToggle
для функций, которые имеют четкое состояние включения/выключения, или ControlValueProvider
для актуальной информации.
Счастливого виджетирования!