Streaming is available in most browsers,
and in the Developer app.
-
Extend your app’s controls across the system
Bring your app's controls to Control Center, the Lock Screen, and beyond. Learn how you can use WidgetKit to extend your app's controls to the system experience. We'll cover how you can to build a control, tailor its appearance, and make it configurable.
Chapters
- 0:00 - Introduction
- 0:37 - Learn about controls
- 3:04 - Build a control
- 6:39 - Update toggle states
- 12:25 - Make controls configurable
- 14:40 - Add refinements
Resources
- Adding refinements and configuration to controls
- Creating a camera experience for the Lock Screen
- Creating controls to perform actions across the system
- Forum: App & System Services
- Human Interface Guidelines: Controls
- Updating controls locally and remotely
Related Videos
WWDC24
-
DownloadArray
-
-
3:13 - Add the control to the Widget Bundle
@main struct ProductivityExtensionBundle: WidgetBundle { var body: some Widget { ChecklistWidget() TaskCounterWidget() TimerToggle() } }
-
3:29 - Complete the control
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.apple.Productivity.TimerToggle" ) { ControlWidgetToggle( "Work Timer", isOn: TimerManager.shared.isRunning, action: ToggleTimerIntent() ) { _ in Image(systemName: "hourglass.bottomhalf.filled") } } } }
-
4:41 - Specify different symbols when on and off
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.apple.Productivity.TimerToggle" ) { ControlWidgetToggle( "Work Timer", isOn: TimerManager.shared.isRunning, action: ToggleTimerIntent() ) { isOn in Image(systemName: isOn ? "hourglass" : "hourglass.bottomhalf.filled") } } } }
-
5:21 - Specify custom value text and add a custom tint color
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.apple.Productivity.TimerToggle" ) { ControlWidgetToggle( "Work Timer", isOn: TimerManager.shared.isRunning, action: ToggleTimerIntent() ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: isOn ? "hourglass" : "hourglass.bottomhalf.filled") } .tint(.purple) } } }
-
8:14 - Implement timer toggling
struct ToggleTimerIntent: SetValueIntent, LiveActivityIntent { static let title: LocalizedStringResource = "Productivity Timer" @Parameter(title: "Running") var value: Bool // The timer’s running state func perform() throws -> some IntentResult { TimerManager.shared.setTimerRunning(value) return .result() } }
-
8:54 - Refresh the control from within the app
func timerManager(_ manager: TimerManager, timerDidChange timer: ProductivityTimer) { ControlCenter.shared.reloadControls( ofKind: "com.apple.Productivity.TimerToggle" ) }
-
10:03 - Define a Value Provider
struct TimerValueProvider: ControlValueProvider { func currentValue() async throws -> Bool { try await TimerManager.shared.fetchRunningState() } let previewValue: Bool = false }
-
11:00 - Provide asynchronously fetched state with a Value Provider
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.apple.Productivity.TimerToggle", provider: TimerValueProvider() ) { isRunning in ControlWidgetToggle( "Work Timer", isOn: isRunning, action: ToggleTimerIntent() ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: isOn ? "hourglass" : "hourglass.bottomhalf.filled") } .tint(.purple) } } }
-
13:06 - Make the Value Provider configurable
struct ConfigurableTimerValueProvider: AppIntentControlValueProvider { func currentValue(configuration: SelectTimerIntent) async throws -> TimerState { let timer = configuration.timer let isRunning = try await TimerManager.shared.fetchTimerRunning(timer: timer) return TimerState(timer: timer, isRunning: isRunning) } func previewValue(configuration: SelectTimerIntent) -> TimerState { return TimerState(timer: configuration.timer, isRunning: false) } }
-
13:40 - Make the timer configurable
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( kind: "com.apple.Productivity.TimerToggle", provider: ConfigurableTimerValueProvider() ) { timerState in ControlWidgetToggle( timerState.timer.name, isOn: timerState.isRunning, action: ToggleTimerIntent(timer: timerState.timer) ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: isOn ? "hourglass" : "hourglass.bottomhalf.filled") } .tint(.purple) } } }
-
14:26 - Prompt for user configuration automatically
struct SomeControl: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( // ... ) .promptsForUserConfiguration() } }
-
15:42 - Custom action hint -> hint treated as verb phrase
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( kind: "com.apple.Productivity.TimerToggle", provider: ConfigurableTimerValueProvider() ) { timerState in ControlWidgetToggle( timerState.timer.name, isOn: timerState.isRunning, action: ToggleTimerIntent(timer: timerState.timer) ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: isOn ? "hourglass" : "hourglass.bottomhalf.filled") .controlWidgetActionHint(isOn ? "Start" : "Stop") } .tint(.purple) } } }
-
16:56 - Specify a display name and add a description
struct TimerToggle: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( kind: "com.apple.Productivity.TimerToggle", provider: ConfigurableTimerValueProvider() ) { timerState in ControlWidgetToggle( timerState.timer.name, isOn: timerState.isRunning, action: ToggleTimerIntent(timer: timerState.timer) ) { isOn in Label(isOn ? "Running" : "Stopped", systemImage: isOn ? "hourglass" : "hourglass.bottomhalf.filled") .controlWidgetActionHint(isOn ? "Start" : "Stop") } .tint(.purple) } .displayName("Productivity Timer") .description("Start and stop a productivity timer.") } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.