1、概念
1.1 在主屏幕、通知中心、锁屏界面(iOS16)显示的小组件
1.2 小组件的样式大小是固定的,iOS下目前有6中样式,可读取环境变量获取widgetFamily
1.3小组件支持点击跳转、支持基础控件,不支持滑动和交互
小组件的点击默认是打开app
可以通过widgetURL或者Link(支持medium、large)去区分点击事件.onOpenURL { url in print("\(url)") }
1.4 iOS锁屏界面小组件的渲染模式是
vibrant
,去饱和度显示,可读取环境变量获取widgetRenderingMode
1.5 小组件只支持SwiftUI部分控件,不支持桥接的UIKit控件,OC应用也可以添加小组件
1.6 用户必须在安装应用程序后至少启动一次才可以使用小组件
2、使用
2.1 创建Widget Extension
复选框表示是否需要自定义小组件,如果是使用静态小组件配置,请不要勾选这个选项。
2.2 点击finish生成小组件模版
//1、kind:小组件的名称,用于区分小组件,app刷新小组件
//2、intent:可编辑小组件的配置,编译后编译器生成的
//3、provider:timelineProvider,提供占位,预览,显示数据
//4、content:显示数据的试图
//5、displayName:小组件的展示名称,在预览页会显示
//6、description:小组件的描述,在预览页会显示
@main
struct IntentWidget: Widget {
var body: some WidgetConfiguration {
IntentConfiguration(kind: "IntentWidget", intent: ConfigurationIntent.self, provider: Provider()) { entry in
IntentWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct Provider: IntentTimelineProvider {
//占位数据,添加小组件之后的占位数据,首次才会显示
//占位数据显示的时机:
//1、timeline提供的数据有延时时
//2、默认是显示骨架图的,需要调用控件的`.unredacted()`
func placeholder(in context: Context) -> SimpleEntry {}
//预览图,提供准备添加小组件时显示的数据
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {}
//提供数据,以及刷新策略`TimelineReloadPolicy`
//1.atEnd:timeline entries 全部显示后重新加载timeline
//2.never:不需要重新加载timeline
//3.after(_ date: Date):在未来的日期后重新加载timeline
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) {}
}
//模型
//date:在这个时间使用这个模型
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
var relevance: TimelineEntryRelevance?//控制在堆栈小组件中将当前小组件滚动到顶端
}
//视图
//通过环境变量获取当前显示的小组件的类型
struct IntentWidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
var body: some View {
Text(entry.date, style: .time)
}
}
2.4 配置intent
class IntentHandler: INExtension, ConfigurationIntentHandling {
func provideNameOptionsCollection(for intent: ConfigurationIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) {
completion(INObjectCollection(items:["aaa","bbb","ccc"]), nil)
}
override func handler(for intent: INIntent) -> Any {
return self
}
}
注意事项:
IntentHandler
需要实现ConfigurationIntent
协议,会出现找不到协议的error,原因是ConfigurationIntent
编译后的类没有加入到IntentHandler
的target中
3 其他
3.1 小组件刷新
1、widgetkit刷新频次最小间隔5分钟
2、为可预测的事件生成时间线getTimeline
3、应用内刷新小组件
WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")
WidgetCenter.shared.reloadAllTimelines()
3.2 WidgetBundle
//最多五个,可以嵌套使用
@main
struct GameWidgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
GameStatusWidget()
CharacterDetailWidget()
}
}
3.3 Live Activities
1.只支持iPhone
2.使用WidgetKit & SwiftUI
3.更新的动态数据最大4KB
4.最大高度160pt
5.内容更新时自动显示动画
6.必须在应用内开启Live Activity
7.结束或者更新Live Activity可以在应用内或者pushNotification
8.最大活跃时间是8个小时,结束之后最大可以展示4个小时
9.info.plist 配置NSSupportsLiveActivities 为true
1、创建widget
import ActivityKit
import SwiftUI
import WidgetKit
struct PizzaDeliveryAttributes: ActivityAttributes {
public typealias PizzaDeliveryStatus = ContentState
public struct ContentState: Codable, Hashable {
var driverName: String
var estimatedDeliveryTime: Date
}
var numberOfPizzas: Int
var totalAmount: String
}
struct PizzaDeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(attributesType: PizzaDeliveryAttributes.self) { context in
// Create the Lock Screen user interface for your Live Activity.
VStack {
Text("\(context.attributes.numberOfPizzas) ordered for \(context.attributes.totalAmount).")
HStack {
Text("\(context.state.driverName) is on their way with your pizza!")
Text(context.state.estimatedDeliveryTime, style: .timer)
}
}.activityBackgroundTint(Color.cyan)
}
}
}
2、开启LiveActivity
let pizzaDeliveryAttributes = PizzaDeliveryAttributes(numberOfPizzas: 42, totalAmount:"$420,-")
// Estimated delivery time is one hour from now.
let initialContentState = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Bill James", estimatedDeliveryTime: Date().addingTimeInterval(60 * 60))
do {
let deliveryActivity = try Activity.request(
attributes: pizzaDeliveryAttributes,
contentState: initialContentState,
pushType: nil)
print("Requested a pizza delivery Live Activity \(deliveryActivity.id)")
} catch (let error) {
print("Error requesting pizza delivery Live Activity \(error.localizedDescription)")
}
3、更新LiveActivity
let updatedDeliveryStatus = PizzaDeliveryStatus(driverName: "Anne Johnson", estimatedDeliveryTime: Date().addingTimeInterval(60 * 60))
do {
try await deliveryActivity.update(using: updatedDeliveryStatus)
} catch(let error) {
print("Error updating activity \(error.localizedDescription)")
}
4、结束LiveActivity
let updatedDeliveryStatus = PizzaDeliveryStatus(driverName: "Anne Johnson", estimatedDeliveryTime: Date())
do {
try await deliveryActivity.end(using: updatedDeliveryStatus, dismissalPolicy: .default)
} catch(let error) {
print("Error ending activity \(error.localizedDescription)")
}
ActivityUIDismissalPolicy
static letdefault
: ActivityUIDismissalPolicy
static letimmediate
: ActivityUIDismissalPolicy
static funcafter(Date)
-> ActivityUIDismissalPolicy
4、远程推送更新&结束LiveActivity
{
"aps": {
"timestamp": 1650998941,
"event": "update",
"content-state": {
"driverName": "Anne Johnson",
"estimatedDeliveryTime": 1659416400
}
}
}
4、参考链接
https://developer.apple.com/documentation/widgetkit
https://developer.apple.com/videos/all-videos/?q=WidgetKit/
https://medium.com/kinandcartacreated/widgetkit-advanced-development-part-1-dbb0e49e849c
https://medium.com/kinandcartacreated/widgetkit-advanced-development-part-2-a675a617fdc9
https://developer.apple.com/documentation/activitykit/displaying-live-data-on-the-lock-screen-with-live-activities