iOS Widget 支持通过"参数化"配置内容,让用户在添加 Widget 时根据个人偏好选择展示内容。这一功能通过 IntentConfiguration 实现,是打造个性化、可复用小组件的关键。
本篇文章介绍如何使用 IntentConfiguration(基于 .intentdefinition)为 Widget 提供可配置参数------以"选择蔬菜"为示例,讲解从创建 .intentdefinition 文件、生成代码、在 Widget 中使用到实现动态选项(Intents Extension)的完整流程,并给出调试与注意事项。
1. 概念回顾:IntentConfiguration 是什么
IntentConfiguration是 WidgetKit 提供给第三方参数配置的经典方式,基于 Intents 框架。使用时,Widget 编辑界面会自动呈现意图中定义的参数选择器。- 主要三种 WidgetConfiguration:
StaticConfiguration:无配置项,固定展示内容。IntentConfiguration:基于.intentdefinition(Intents),通过 Intent 文件或 Intents extension 提供选项。AppIntentConfiguration:iOS 16 引入的基于AppIntents的现代方式(更推荐在 iOS 16+ 环境使用)。
何时使用 IntentConfiguration:需要支持 iOS 14/15 的项目,或者已有 .intentdefinition 工作流,需要兼容旧系统。
2. 在 Xcode 中创建 .intentdefinition(静态选项)
以下以"选择蔬菜(SelectVegetableIntent)"为例:
在 Project Navigator 中,选择项目或 Widget 的 group,选择 File → New → File from Template。
在模板列表选择 Resource 下的 SiriKit Intent Definition file.

点击 Next,命名为 VegetableCategories.intentdefinition 或 IntentDefinitions.intentdefinition,注意两个 target 都要勾选。

打开该文件,左下角点击 +,选择 New Intent,命名为 VegetableCategories(或你喜欢的名称)。
在这个文件里面,需要更改几个地方,将 Category 设置为 View,顺便将 Description 写一下,记得把 Intent is eligible for widgets 勾选上。

现在点击左下角+号创建一个 Enum, 命名为 Vegetable,并在 Cases 中添加一些类型。

现在选择刚才创建的 VegetableCategories intent,在 Parameters 中创建一个属性 vegetable,并选择类型为刚才创建的 enum。

到此,一个简单的 intentdefinition 文件就创建完了。
那么在项目中如何使用呢?
选中主工程的 target,在 General 下面的 Supported Intents 中添加刚才创建的 VegetableCategoriesIntent。

回到我们 Widget extension 中,修改代码如下:
swift
struct VegetableCategoriesProvider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: VegetableCategoriesIntent())
}
func getSnapshot(for configuration: VegetableCategoriesIntent, in context: Context, completion: @escaping @Sendable (SimpleEntry) -> Void) {
let simpleEntry = SimpleEntry(date: Date(), configuration: configuration)
completion(simpleEntry)
}
func getTimeline(for configuration: VegetableCategoriesIntent, in context: Context, completion: @escaping @Sendable (Timeline<SimpleEntry>) -> Void) {
let vegetableName = vegetableName(for: configuration)
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, configuration: configuration, vegetableName: vegetableName)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
func vegetableName(for configuration: VegetableCategoriesIntent) -> String {
switch configuration.vegetable {
case .carrot:
return "Carrot"
case .broccoli:
return "Broccoli"
case .cucumber:
return "Cucumber"
case .celery:
return "Celery"
case .unknown:
return "Unknown"
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: VegetableCategoriesIntent
var vegetableName: String?
}
struct SimpleWidgetEntryView : View {
var entry: VegetableCategoriesProvider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .timer)
Text("Favorite vegetable:")
Text(entry.vegetableName ?? "未选择")
}
}
}
struct SimpleWidget: Widget {
let kind: String = "SimpleWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: VegetableCategoriesIntent.self, provider: VegetableCategoriesProvider()) { entry in
SimpleWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
}
}
顺利的话编译就通过了,上面代码中修改了以下 UI,将选择的 vegetable 显示出来了。

3. 动态创建 ConfigurationIntent(以 Country Widget 为例)
首先先看几个概念:
- Configuration Intent:用于在 Widget 配置界面(或 Shortcuts UI)提供"参数选择"。
- AppIntents:Apple 在较新 SDK 中提供的替代/补充方案,用于定义可供系统调用的动作与实体。
- AppEntity:在 AppIntents 中表示一个"实体对象"(如联系人、地点或本例的国家),支持被系统识别、建议与持久化。
- EntityQuery:为 AppEntity 提供加载/查找/建议项的方法(同步或异步)。
- parameterSummary:用于在系统 UI 中以简洁的方式展示当前参数的摘要。
Summary { \.$country }会把参数投影(projected)值显示出来。
下面我们创建一个选择国家的 Widget,我们用 CountrySelectIntent(一个 Configuration Intent)作为 Widget 的配置参数,并通过 CountryEntry: AppEntity + CountryEntryQuery: EntityQuery 来提供建议列表与实体解析。Widget 的 TimelineProvider 读取用户选择的实体,渲染对应内容。
3.1 在 Intent 文件中定义实体与参数(CountrySelectIntent.swift)
swift
// CountrySelectIntent: 用于 Widget 配置的 Intent 定义
struct CountrySelectIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Select Country"
static var description: IntentDescription = "Choose a country to display its information in the widget."
// 使用 AppEntity 类型作为参数,可在系统 UI 中显示实体并支持建议列表
@Parameter(title: "Country", description: "select a country")
var country: CountryEntry
// 在 UI 摘要处显示已选的 country(即 CountryEntry 的 displayRepresentation)
static var parameterSummary: some ParameterSummary {
Summary { \.$country }
}
}
关键点说明:
@Parameter的类型是CountryEntry(实现了AppEntity),不是普通的String。这使得系统能展示"实体选择"界面,而不是只能输入文本。parameterSummary的Summary { \.$country }表达式用来告诉系统在 Widget 编辑中如何显示用户当前的选择;\.$country是参数的投影值(projected value),它会渲染为实体的displayRepresentation。
接着看 CountryEntry 与 CountryEntryQuery:
swift
// CountryEntry: 一个轻量的 AppEntity,用来表示"国家"这一实体
struct CountryEntry: AppEntity, Identifiable {
// 系统展示类型名称
static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation(name: "Country")
}
// 默认的查询实现(当系统需要列出/解析此实体时会使用它)
static var defaultQuery = CountryEntryQuery()
// AppEntity 需要一个唯一 id,这里以 countryCode 作为 id
var id: String { countryCode }
let countryCode: String
// 方便在运行时获取一个默认实体(eg. 用作回退值)
static func defaultValue() -> CountryEntry {
CountryEntry(countryCode: "US")
}
// 给调用方暴露一个友好的 name
var countName: String {
Self.names[countryCode] ?? countryCode
}
// 国家代码 -> 名称 映射(可扩展为更多国家或国际化)
private static let names: [String: String] = [
"US": "United States",
"CA": "Canada",
"GB": "United Kingdom",
"FR": "France",
"DE": "Germany"
]
// 用于 AppIntents/UI 的显示;系统会把这个字符串展示在选择/摘要中
var displayRepresentation: DisplayRepresentation {
let name = Self.names[countryCode] ?? countryCode
// 这里使用简单的 stringLiteral 表示:"United States (US)"
return DisplayRepresentation(stringLiteral: "\(name) (\(countryCode))")
}
}
// CountryEntryQuery: 实现 EntityQuery
struct CountryEntryQuery: EntityQuery {
typealias EntityType = CountryEntry
// 静态的示例数据(可替换为从网络或数据库加载的动态列表)
var dataList: [CountryEntry] = [
CountryEntry(countryCode: "US"),
CountryEntry(countryCode: "CA"),
CountryEntry(countryCode: "GB"),
CountryEntry(countryCode: "FR"),
CountryEntry(countryCode: "DE")
]
// 系统使用的默认 query
static var defaultQuery: CountryEntryQuery {
return CountryEntryQuery()
}
// entities(for:): 给定一组标识符,返回对应实体。通常用于反解析已保存的配置。
func entities(for identifiers: [String]) async throws -> [CountryEntry] {
return identifiers.map { CountryEntry(countryCode: $0) }
}
// suggestedEntities(): 返回供 UI 显示的建议实体列表(可以是静态或动态的)
func suggestedEntities() async throws -> [CountryEntry] {
return dataList
}
}
说明:
CountryEntry用countryCode作为id,并实现displayRepresentation返回可读文本(系统用于 UI 展示)。CountryEntryQuery的suggestedEntities()返回系统将用来显示在选择界面的建议项(本示例返回静态列表,但可以改为异步从网络加载)。entities(for:)用于把标识符数组还原为实体(系统在持久化/恢复配置时会调用)。
3.2 在 Widget 中使用 Configuration Intent
Widget 使用 AppIntentConfiguration(或 IntentConfiguration)来指定 intent 类型与 provider:
swift
struct CountryProvider: AppIntentTimelineProvider {
typealias Intent = CountrySelectIntent
typealias Entry = CountryTimelineEntry
func placeholder(in context: Context) -> CountryTimelineEntry {
CountryTimelineEntry(date: Date(), configuration: CountrySelectIntent(), country: CountryEntry(countryCode: "US"))
}
func snapshot(for configuration: CountrySelectIntent, in context: Context) async -> CountryTimelineEntry {
CountryTimelineEntry(date: Date(), configuration: CountrySelectIntent(), country: CountryEntry(countryCode: "US"))
}
func timeline(for configuration: CountrySelectIntent, in context: Context) async -> Timeline<CountryTimelineEntry> {
let currentDate = Date()
let entry = CountryTimelineEntry(date: currentDate, configuration: configuration, country: configuration.country)
let timeline = Timeline(entries: [entry], policy: .atEnd)
return timeline
}
}
struct CountryTimelineEntry: TimelineEntry {
let date: Date
let configuration: CountrySelectIntent
let country: CountryEntry
}
struct CountryView: View {
var entry: CountryProvider.Entry
var body: some View {
VStack {
Text("Country:")
Text(entry.country.countName)
.font(.headline)
}
}
}
struct CountryWidget: Widget {
let kind: String = "CountryWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, intent: CountrySelectIntent.self, provider: CountryProvider()) { entry in
CountryView(entry: entry)
}
.configurationDisplayName("Country Widget")
.description("Displays information about the selected country.")
}
}
关键点:
CountryProvider.timeline(for:in:)的configuration参数是CountrySelectIntent,并且configuration.country是CountryEntry实例(由系统生成/反序列化),可以直接用于渲染。- 在
placeholder/snapshot中,建议提供合理值。
使用示例仅仅是为了使用 Intent,并未对数据进行本地缓存以及一些其他的优化。

4. 本地化、性能与安全注意事项
- 本地化:在
.intentdefinition中为每个 Item 设置本地化的 displayString,或在 Localizable.strings 中提供翻译。 - 性能:Intents extension 运行时间短,避免同步等待网络请求;若确需网络,使用缓存(App Group)并优先返回缓存结果。
- 权限与隐私:如果动态选项涉及用户隐私数据(联系人、日历等),在主 App 中请求并获取权限,Intents extension 需谨慎处理权限敏感操作。
5. AppIntents 的简要对比与迁移建议
- iOS 16 引入了
AppIntents,它使用 Swift 原生 API 编写 Intent,替代了.intentdefinition的可视化工作流。 - 优点:写 Swift 代码更直观,测试更方便。缺点:最低支持 iOS 16。
- 迁移建议:如果你的应用仅面向 iOS 16+,优先考虑
AppIntentConfiguration;若需兼容 iOS 14/15 或已有大量.intentdefinition,继续使用 Intents 文件。
6. 总结
- 使用
IntentConfiguration可以为 Widget 提供用户可配置的参数选择,适用于需要兼容较旧 iOS 版本或已有.intentdefinition的项目。 - 静态选项简单直接:直接在
.intentdefinition文件中添加 Items 并生成代码。 - 动态选项需实现相应的 Intents 协议及回调,注意性能与缓存策略。
- 推荐实践:把图片资源放在 Widget 的 Assets、把耗时网络操作迁移到 App 并通过 App Group 缓存结果、在
.intentdefinition中做好本地化。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。