在 iOS18 中,你可以将应用的控件扩展到系统级别,使其出现在控制中心、锁定屏幕等位置。本文将详细介绍如何使用 WidgetKit 构建和定制控件,并让控件支持配置,最终将其添加到系统界面中。
一、前期准备在开始之前,确保你已经在 Xcode 中创建了一个 iOS 项目,并安装了最新版本的 Xcode。你还需要一些基本的 Swift 和 SwiftUI 知识。
二、将控件添加到 Widget Bundle1. 什么是 Widget Bundle?Widget Bundle 是一种容器,允许你将多个控件组合在一起。通过将控件添加到 Widget Bundle,你可以更好地组织和管理这些控件,并方便地在应用中启用或禁用它们。
2. 创建一个 Widget Bundle我们需要定义一个 WidgetBundle 来包含我们的控件。以下是具体的代码示例:
12345678910@mainstruct ProductivityExtensionBundle: WidgetBundle { var body: some Widget { ChecklistWidget() // 添加清单控件 TaskCounterWidget() // 添加任务计数控件 TimerToggle() // 添加定时器切换控件 } }
@main 标记:表示这是应用的入口点。它将这个 WidgetBundle 注册为应用的一部分。
ProductivityExtensionBundle:这是我们定义的 WidgetBundle 名称。
var body: some Widget:这个属性表示该 WidgetBundle 中包含的控件列表。你可以在这里添加任意数量的控件。
3. 控件的添加在 body 中,我们添加了三个控件:
ChecklistWidget:一个清单控件,用于显示任务列表。
TaskCounterWidget:一个任务计数控件,用于显示当前任务的数量。
TimerToggle:一个定时器切换控件,用于控制定时器的启动和停止。
通过这种方式,你可以将多个控件打包在一起,使它们可以在应用的不同部分方便地使用。
三、构建控件1. 创建控件的基础结构我们将使用 ControlWidget 协议来定义一个基础控件。这里我们以定时器切换控件为例进行说明。
1234567891011121314151617struct 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") } } }}
struct TimerToggle: ControlWidget:定义了一个新的结构体 TimerToggle,它实现了 ControlWidget 协议。
StaticControlConfiguration:这是一个静态配置,用于定义控件的外观和行为。
kind:指定控件的唯一标识符,这里使用了 "com.apple.Productivity.TimerToggle"。
ControlWidgetToggle:定义了一个切换控件,使用 isOn 属性表示当前的状态。
isOn:绑定到 TimerManager.shared.isRunning,表示定时器是否在运行。
action:指定了一个 ToggleTimerIntent 操作,当用户点击控件时触发该操作。
{ isOn in ... }:这是一个闭包,用于根据 isOn 的值动态更新控件的图标。
2. 控件的工作原理这个控件的核心是一个切换按钮。根据定时器的运行状态(isOn),显示不同的图标(如沙漏或空心沙漏),并执行相应的操作(启动或停止定时器)。
3. 控件配置的关键点
StaticControlConfiguration:静态配置,用于定义控件的结构。
ControlWidgetToggle:切换控件,具有 isOn 属性和操作意图 action。
通过这种方式,你可以创建一个简单而功能强大的控件,并根据需要自定义其外观和行为。
四、自定义控件外观1. 指定不同的符号为了增强用户体验,我们可以根据定时器的状态显示不同的符号(例如:定时器运行时显示沙漏,停止时显示半沙漏)。
1234567891011121314151617struct 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") } } }}
Image(systemName:):这是一个 SwiftUI中的图像控件,用于显示系统图标。
isOn:根据 isOn 的值选择不同的图标。
systemName: isOn ? "hourglass" : "hourglass.bottomhalf.filled":如果定时器在运行,显示沙漏图标;否则显示半沙漏图标。
2. 添加自定义文本和颜色你还可以为控件添加自定义文本和颜色,以提供更好的视觉效果和用户提示。
12345678910111213141516171819struct 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) } }}
Label:这是一个带有文本和图标的控件,用于显示控件的状态。
isOn ? "Running" : "Stopped":根据 isOn 的值选择显示文本,运行时显示“Running”,停止时显示“Stopped”。
systemImage:根据 isOn 的值选择不同的图标。
.tint(.purple):设置控件的主题色为紫色,使其更具视觉吸引力。
通过这些自定义选项,你可以创建一个更加丰富和直观的控件。
五、实现控件功能1. 定义定时器切换逻辑我们需要实现一个操作意图(Intent),用于处理定时器的启动和停止操作。
1234567891011struct ToggleTimerIntent: SetValueIntent, LiveActivityIntent { static let title: LocalizedStringResource = "Productivity Timer" @Parameter(title: "Running") var value: Bool // 定时器的运行状态 func perform() throws -> some IntentResult { TimerManager.shared.setTimerRunning(value) return .result() }}
struct ToggleTimerIntent: SetValueIntent, LiveActivityIntent:定义了一个新的结构体 ToggleTimerIntent,它实现了 SetValueIntent 和 LiveActivityIntent 协议。
@Parameter:这是一个参数注解,用于定义意图中的参数。
title: "Running":参数的标题。
var value: Bool:一个布尔值,表示定时器的运行状态。
func perform() throws -> some IntentResult:定义了执行意图时的操作。
TimerManager.shared.setTimerRunning(value):调用 TimerManager 来设置定时器的运行状态。
return .result():返回一个结果,表示意图执行成功。
这个意图允许用户通过控件启动或停止定时器,并将状态更新到控件中。
2. 从应用内部刷新控件当定时器状态发生变化时,我们需要刷新控件的显示,以确保其显示的状态与实际一致。
123456func timerManager(_ manager: TimerManager, timerDidChange timer: ProductivityTimer) { ControlCenter.shared.reloadControls( ofKind: "com.apple.Productivity.TimerToggle" )}
func timerManager(_ manager: TimerManager, timerDidChange timer: ProductivityTimer):这是一个回调函数,当定时器状态发生变化时调用。
ControlCenter.shared.reloadControls(ofKind: "com.apple.Productivity.TimerToggle"):调用 ControlCenter 来刷新指定类型的控件,这里是 TimerToggle。
通过这种方式,你可以确保控件的显示始终与定时器的实际状态保持一致。
六、值提供者与异步数据获取1. 定义值提供者为了提高控件的灵活性和响应性,我们可以使用值提供者(Value Provider)来动态获取控件的状态。
12345678struct TimerValueProvider: ControlValueProvider { func currentValue() async throws -> Bool { try await TimerManager.shared.fetchRunningState() } let previewValue: Bool = false}
struct TimerValueProvider: ControlValueProvider:定义了一个新的结构体 TimerValueProvider,它实现了 ControlValueProvider 协议。
func currentValue() async throws -> Bool:这是一个异步函数,用于获取当前的定时器状态。
try await TimerManager.shared.fetchRunningState():调用 TimerManager 的异步方法,获取定时器的运行状态。
let previewValue: Bool = false:定义了一个预览值,当无法获取实际值时使用。
2. 异步获取状态我们可以将值提供者集成到控件中,以实现异步数据获取。
1234567891011121314151617181920struct 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) } }}
provider: TimerValueProvider():使用我们定义的 TimerValueProvider 作为值提供者。
{ isRunning in ... }:这是一个闭包,当获取到定时器状态时执行,其中 isRunning 表示当前的定时器状态。
通过这种方式,你可以使控件动态响应定时器状态的变化,提供更好的用户体验。
七、使控件可配置1. 定义可配置的值提供者我们可以进一步扩展值提供者,使其支持配置不同的定时器。
1234567891011struct 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) }}
struct ConfigurableTimerValueProvider: AppIntentControlValueProvider:定义了一个可配置的值提供者,实现了 AppIntentControlValueProvider 协议。
func currentValue(configuration: SelectTimerIntent) async throws -> TimerState:这是一个异步函数,根据配置获取当前的定时器状态。
let timer = configuration.timer:获取配置中的定时器。
let isRunning = try await TimerManager.shared.fetchTimerRunning(timer: timer):获取指定定时器的运行状态。
func previewValue(configuration: SelectTimerIntent) -> TimerState:定义了一个预览值,用于在无法获取实际值时使用。
2. 实现可配置的定时器控件我们可以使用可配置的值提供者创建一个更加灵活的定时器控件。
1234567891011121314151617181920struct 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) } }}
AppIntentControlConfiguration:这是一个支持应用意图的控件配置,允许根据用户配置动态更新控件。
{ timerState in ... }:这是一个闭包,根据获取到的定时器状态(timerState)来更新控件。
通过这种方式,你可以创建一个更加灵活和强大的控件,允许用户根据需要配置不同的定时器。
八、自动提示用户配置1. 自动提示用户配置为了提高用户体验,我们可以自动提示用户对控件进行配置。
12345678struct SomeControl: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( // 配置项 ) .promptsForUserConfiguration() }}
.promptsForUserConfiguration():这是一个方法,调用它可以在控件首次使用时自动提示用户进行配置。
通过这种方式,你可以让用户在使用控件时更加方便地进行配置,提升应用的易用性。
九、添加控件提示与描述1. 添加操作提示和描述为了让用户更好地理解控件的功能,我们可以为控件添加操作提示和描述。
123456789101112131415161718192021222324struct 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.") }}
.controlWidgetActionHint:这是一个方法,用于为控件添加操作提示,如“Start”或“Stop”。
.displayName("Productivity Timer"):设置控件的显示名称为“Productivity Timer”。
.description("Start and stop a productivity timer."):添加控件的描述,说明其功能是启动和停止生产力定时器。
通过这些提示和描述,你可以让用户更清楚地了解控件的用途和操作方法,提升应用的用户体验。
参考视频将 App 控件扩展到系统级别