在 iOS 14 正式版发布之前我写了一篇博文《iOS开发之WidgetKit》,iOS 14 正式版发布以后,经测试,Apple 改变了 Widget 的 API,所以本文进行一个补充说明(在前文的基础上做了修改,尤其是代码部分)。
Widget 寄宿于 App,所以首先必须将 App 功能实现。
File > New > Target
。Application Extension
中,选择Widget Extension
,然后点击Next
。Finish
。扩展名.swift
扩展名.intentdefinition
Assets.xcassets
Info.plist
App 与 Widget 可以通过网络数据和本地数据两种方式进行数据的共享。
Signing&Capabilities
中打开App Groups
,内容一般为group.Bundle Identifier
。Signing&Capabilities
中打开App Groups
,内容与 App 保持一致。配置App Groups.jpg
如果文件需要共享,可以选中 App 中需要共享给 Widget 的文件,然后勾选 Widget 的 Target。
配置完成以后,可以通过UserDefaults
或FileManager
来实现 App 与 Widget 的数据共享,这里以UserDefaults
为例,因为 SwiftUI 提供了@AppStorage
来简化操作。
// 包含App Groups的UserDefaults
@AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget"))
// 然后在后面保存数据
@AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget"))
// 然后在后面取出数据
Timelines
为 Views 提供对应时间所需的数据,当数据变化时,通过reload
更新数据。TimelineProvider
提供一组TimelineEntry
和ReloadPolicy
,用来后续刷新页面。@main
struct UserWidget: Widget {
private let kind: String = "UserWidget"
public var body: some WidgetConfiguration {
}
}
kind
:字符串,唯一标识 Widget。WidgetConfiguration
:有两类配置,分别为 StaticConfiguration
: 可以在不需要用户任何输入的情况下自行解析,可以在 Widget 的 App 中获取相关数据并发送给 Widget。IntentConfiguration
:依赖于 App 的 Siri Intent,会自动接收这些 Intent 并用于更新 Widget,用于构建动态 Widget。.configurationDisplayName
:设置 Widget 在添加界面中显示的标题。.description
::设置 Widget 在添加界面中显示的描述。.supportedFamilies
:设置支持的不同尺寸,可以支持 3 种尺寸,示意图如下。不同尺寸.jpg
不论是哪种配置,都需要提供以下内容。
渲染 Widget 所需的数据模型,需要遵守TimelineEntry
协议。
struct Model: TimelineEntry {
let date: Date
// 显示的内容Model
}
遵守TimelineProvider
协议,告诉 WidgetKit 何时渲染与刷新 Widget。需要实现以下 3 个方法:
struct Provider: TimelineProvider {
// 占位视图,是一个标准的 SwiftUI View,当第一次展示或者发生错误时都会展示该 View。
func placeholder(in context: Context) -> TimelineEntry {
}
// 编辑屏幕在左上角选择添加Widget、第一次展示时会调用该方法
func getSnapshot(in context: Context, completion: @escaping (TimelineEntry) -> Void) {
}
// 进行数据的预处理,转化成Entry
// 最后一定要调用 completion,进而刷新Widget
func getTimeline(in context: Context, completion: @escaping (Timeline<TimelineEntry>) -> Void) {
}
}
.never(不刷新),.atEnd(Entry 显示完毕之后自动刷新) 或 .after(date)(到达某个特定时间后自动刷新)
。WidgetCenter.shared.reloadAllTimelines()
。屏幕上 Widget 显示的内容,可以针对不同尺寸的 Widget 设置不同的 View。
struct EntryView: View {
var entry: Provider.Entry // 数据模型
@Environment(\.widgetFamily) var family // 尺寸环境变量
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
// 小尺寸
case .systemMedium:
// 中尺寸
default:
// 大尺寸
}
}
}
只能点击,点击会打开 App。也可以通过.widgetURL(myDeeplink)
方法配置当 Widget 被点击时触发哪个 Deep Linking,也可以通过使用链接使 Widget 的不同部分触发不同的 Deep Linking(可以直接理解为 Widget 只是一个按钮,点按这个按钮会跳转到指定 URL 对应的页面)。
// 某个Widget内容
var body: some View {
VStack {
Link(destination: homeDeepLink) {
Text("主页")
}
Link(destination: settingsDeepLink) {
Text("设置")
}
}.widgetURL(myDeeplink)
}
@main
struct SwiftUIApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
print(url)
})
}
}
}