了解Widget
官方学习WidgetKit Demo
Widgets显示相关的、可浏览的内容,允许用户快速访问你的App以获取更多详细信息。你的App可以提供多种小组件,让用户专注于对他们最重要的信息。
向你的应用添加一个小组件需要少量的设置,以及一些关于你的用户界面的配置和风格的决定。小组件使用SwiftUI视图显示其内容。有关详细信息,请参见SwiftUI。
添加一个Widget到你的App
Widget Extension模板为创建小组件提供了一个起点。一个Widget Extension可以包含多种类型的小组件。例如,一个体育应用程序可能有一个小组件显示团队信息,另一个小组件显示比赛日程,这个例子一个Widget Extension包含了两个组件。建议在一个Widget Extension中包含你的App所有小组件。
添加步骤如下:
- 在Xcode中打开你的项目,然后选择 File > New > Target 。
-
从“Application Extension”组中,选择“Widget Extension”,然后单击“Next”。如下图:
-
然后,给小组件命令,如下:
- 如果小组件提供用户可配置的属性,请选中include configuration intent复选框。
-
点击Finish,会生成你们组件文件,如:AppWidget.swift、AppWidget.intentdefinition、Assets.xcassets、Info.plist 。如下图:
然后你可以运行到iOS14的设备上看效果,会看到默认的小时钟组件。
配置描述
widget extension模板提供了一个符合widget协议的初始widget实现。此小组件的body属性决定小组件是否具有用户可配置的属性。有两种配置:
-
StaticConfiguration
:对于没有用户可配置属性的小组件。例如,显示一般市场信息的股市小组件,或显示趋势标题的新闻小组件。 -
IntentConfiguration
:对于具有用户可配置属性的小组件。你可以使用SiriKit自定义内容来定义属性。例如,需要一个城市的邮政编码的天气小组件,或者需要跟踪号码的包裹跟踪小组件。
要初始化配置,请提供以下信息:
- Kind: 标识小部件的字符串。这是一个你选择的标识符,应该描述小部件所代表的内容。
- Provider:一个符合TimelineProvider的对象,它生成一个时间轴,告诉WidgetKit何时呈现小组件。时间线包含您定义的自定义TimelineEntry类型。时间线条目标识您希望WidgetKit更新小组件内容的日期。包括小组件视图需要在自定义类型中呈现的属性。
- Placeholder View: WidgetKit第一次使用SwiftUI视图呈现小组件。占位符是小组件的通用表示,没有特定的配置或数据。
- Content Closure: 包含SwiftUI视图的闭包。WidgetKit调用它来呈现小组件的内容,从提供者传递一个TimelineEntry参数。
- Custom Intent: 定义用户可配置属性的自定义意图。有关添加自定义更多信息,请参阅 Making a Configurable Widget。
例如下面代码显示IntentConfiguration类型的组件:
@main
struct AppWidget: Widget {
private let kind: String = "AppWidget" //小组件的标识
public var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider(), placeholder: PlaceholderView()) { entry in
AppWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget") //小组件的名称
.description("This is an example widget.") //小组件的描述
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) //用户选择小部件、中版本或大版本的组件
}
}
在上面代码中,小组件使用PlaceholderView()作为占位符视图,并在内容闭包中使用AppWidgetEntryView。占位符视图显示小组件的通用表示,让用户大致了解小部件显示的内容。不要在占位符视图中包含实际数据。例如,使用灰色框表示文本行,或使用灰色圆表示图像。
Provider为小组件生成一个时间轴,当每个时间轴条目的日期到达时,WidgetKit调用content闭包来更新显示小组件的内容。
注意这个小组件上@main属性的用法。此属性指示AppWidget是小组件扩展的入口点,表示该扩展包含一个小组件。
TimelineProvider
TimelineEntry为TimelineProvider提供数据信息来源,如下所示:
struct SimpleEntry: TimelineEntry {
public let date: Date
public let configuration: ConfigurationIntent
}
为了在窗口小组件库中显示小组件,WidgetKit要求提供者提供预览快照。你可以通过检查此 snapshot(for:with:completion:)
方法中context的isPreview属性来判断小组件显示情况。当isPreview为true时,WidgetKit会在widget库中显示你的widget。对于有网络数据的话,你需要先提供一个默认数据显示快照。如下示例代码:
struct Provider: IntentTimelineProvider {
public func snapshot(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (SimpleEntry) -> ()) {
print(context.isPreview) //显示widget的显示情况
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
}
WidgetKit框架要求我们用timeline(for:with:completion:)
来控制刷新小组件的时间。
struct Provider: IntentTimelineProvider {
public func snapshot(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (SimpleEntry) -> ()) {
print(context.isPreview) //显示widget的显示情况
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
// Create the timeline with the entry and a reload policy with the date
// for the next update.
let timeline = Timeline(entries: entries, policy: .atEnd)
// Call the completion to pass the timeline to WidgetKit.
completion(timeline)
}
}
小组件的显示内容
小组件UI使用SwiftUI来编写的,当用户从widget库中添加你的widget时,他们会从widget支持的组件中选择特定的组件(小型、中型或大型),widget的内容闭包必须能够呈现widget支持的每个组件。根据组件类型可以不同的实现,代码示例如下:
struct AppWidgetEntryView : View {
@Environment(\.widgetFamily) var family: WidgetFamily
var entry: Provider.Entry
//view使用@ViewBuilder声明,因为它使用的view类型不同。
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
Text(entry.date, style: .time)
case .systemMedium:
Text(entry.date, style: .time)
case .systemLarge:
Text(entry.date, style: .time)
default:
Text(entry.date, style: .time)
}
}
}
小组件显示只读信息,不支持交互元素,如滚动元素或开关。
在一个Widget Extension创建多个小组件
要支持多个小组件,需要声明一个符合WidgetBundle的结构,WidgetBundle在其body属性中将多个小组件组合在一起。在这个Widget bundle结构上添加@main属性,告诉WidgetKit你的扩展支持多个widget。代码示例:
@main
struct AppWidgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
AppWidget()
//可以继续添加其它Widget
}
}