Widget 类似一个迷你版的 App,可以快速访问 App 提供的信息—比如天气、事件、笔记等。Widget 还可以充当“快捷方式”,点击它会立即跳转到 App 的指定位置。
介绍
- WidgetKit 通过在 iOS 主屏幕或 macOS 通知中心放置小部件,让用户可以随时访问 App 中的内容。Widget 可以保持更新,从而让用户获得最新信息。当需要查看 App 的更多细节时,Widget 会直接跳转到 App 中的适当位置。
- Widget 有三种不同的尺寸(小号、中号和大号),可以对 Widget 进行个性化定制。
- 要实现一个 Widget,需要给应用添加一个 Widget 扩展并只能使用 SwiftUI 来实现 Widget 的内容。
App实现
Widget 寄宿于 App,所以首先必须将 App 功能实现。
添加Widget
- 点击项目,选择File > New > Target。
- 从Application Extension中,选择Widget Extension,然后点击Next。
- 输入扩展名的名称。
- 单击Finish。
- 此时会生成一个新文件夹,包含以下内容
- 扩展名.swift
- 扩展名.intentdefinition
- Assets.xcassets
- Info.plist
数据共享
App 与 Widget 可以通过网络数据和本地数据两种方式进行数据的共享。
- 网络数据可通过 URLSession 完成数据的请求与解析。
- 本地数据共享可以通过 App Groups,它是 iOS 8 之后推出的在 App 之间共享数据的方式,只需要简单的配置就可以实现数据的共享。(本文以此为例)
配置
- App 在Signing&Capabilities中打开App Groups,内容一般为group.Bundle Identifier。
- Widget 必须在Signing&Capabilities中打开App Groups,内容与 App 保持一致。
如果文件需要共享,可以选中 App 中需要共享给 Widget 的文件,然后勾选 Widget 的 Target。
实现
配置完成以后,可以通过UserDefaults或FileManager来实现 App 与 Widget 的数据共享,这里以UserDefaults为例,因为 SwiftUI 提供了@AppStorage来简化操作。
- App
// 包含App Groups的UserDefaults @AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget")) // 然后在后面保存数据
- Widget
@AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget")) // 然后在后面取出数据
编写Widget
- 原理:开发者通过 SwiftUI 构建 Views,定义Timelines为 Views 提供对应时间所需的数据,当数据变化时,通过reload更新数据。TimelineProvider提供一组TimelineEntry和ReloadPolicy,用来后续刷新页面。
- 实现 Widget 的代码相对比较模版,可以从 Widget 的入口开始,缺什么补什么。
入口
@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。
- .supportedFamilies:支持不同尺寸。
内容
不论是哪种配置,都需要提供以下内容。
Entry
渲染 Widget 所需的数据模型,需要遵守TimelineEntry协议。
struct Model: TimelineEntry { let date: Date // 显示的内容Model }
Provider
遵守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) { } }
- getTimeline 是最重要的方法,后面的数据刷新都会在其中完成,所以可能会在其中完成最新的网络数据和本地数据的获取,然后转成 Model 以供使用。
- getTimeline 的方法里有一个 policy 参数,表示刷新的时机,可以选择.never(不刷新),.atEnd(Entry 显示完毕之后自动刷新) 或 .after(date)(到达某个特定时间后自动刷新)。
- Widget 刷新的时间由系统统一决定(有时候设置了也不会自己刷新),如果需要强制刷新 Widget,可以在 App 中使用 WidgetCenter 来重新加载所有时间线:WidgetCenter.shared.reloadAllTimelines()。
EntryView
屏幕上 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: // 大尺寸 } } }
- Widget 能且只能使用 SwiftUI 构建界面。
- Widget 本质:一个随着时间线而更新的 SwiftUI View。
运行
- 先运行 App
- 再运行 Widget
交互
只能点击,点击会打开 App。也可以通过.widgetURL(myDeeplink)方法配置当 Widget 被点击时触发哪个 Deep Linking,也可以通过使用链接使 Widget 的不同部分触发不同的 Deep Linking(可以直接理解为 Widget 只是一个按钮,点按这个按钮会跳转到指定 URL 对应的页面)。
- Widget点击
// 某个Widget内容 var body: some View { VStack { Link(destination: homeDeepLink) { Text("主页") } Link(destination: settingsDeepLink) { Text("设置") } }.widgetURL(myDeeplink) }
- App处理
@main struct SwiftUIApp: App { var body: some Scene { WindowGroup { ContentView() .onOpenURL(perform: { url in print(url) }) } } }
源代码
👇推荐👇:
大家可以加入iOS技术交流群,群号:642363427 群内提供数据结构与算法、底层进阶、swift、逆向、底层面试题整合文档等免费资料!!!
原文地址:yungfan