实现实时活动和灵动岛
1 概览
实时活动是在 iOS 16.1 上面推出的新 API,用于在锁屏和灵动岛上显示最新数据。为了实现实时活动,需要在小部件实现。实时活动使用 WidgetKit 和 SwiftUI 来构建用户界面,而 ActivityKit 则用于处理实时活动的生命周期——请求、更新、和结束。
2 实时活动的要求和限制
实时活动最多能保持 8 小时,超过时系统会自动结束。当实时活动结束时,会从灵动岛上移除,但是会留在锁屏上最多 4 小时。因此理论上来说,实时活动可以在锁屏上最多保留 12 小时。
实时活动运行在独立的沙盒中,不能像小部件一样访问网络或者接收定位更新。所以只能在应用中使用 ActivityKit 或者远程推送通知来刷新活动的数据。
注意
不论使用何种方式更新,其数据的大小不能超过 4KB。
3 为应用添加实时活动支持
添加实时活动只需要如下几步:
- 创建小组件,如果应用中已存在小组件,略过。
- 在主应用的 Info.plist 文件中添加实时活动的支持,设置Supports Live Activities 为 YES。
- 添加 ActivityAttributes 来描述实时活动的数据。
- 使用 ActivityAttributes 创建 ActivityConfiguration。
- 实现实时活动的启动、更新和结束的功能。
此处略过1、2步。
3.1 定义活动数据类型
1 | struct FitnessAttributes: ActivityAttributes { |
上面代码定义了一个健身的实时活动 attributes。其中,FitnessAttributes 中定义的为静态数据类型,而 FitnessState 中所定义的则为动态数据类型。
3.2 创建实时活动的视图
有了实时活动所需的数据类型,接下来就是在灵动岛和锁屏上将其显示出来。
1 | struct FitnessWidget: Widget { |
接下来就来看看以上代码具体是什么意思。
首先是构造一个实时活动的配置并返回,这个方法的签名如下:
1 | public init<Content>(for attributesType: Attributes.Type = Attributes.self, content: @escaping (ActivityViewContext<Attributes>) -> Content, dynamicIsland: @escaping (ActivityViewContext<Attributes>) -> DynamicIsland) where Content : View |
方法签名虽然看起来很长,但实际上只有三个参数,第一个是支持的实时活动的数据类型,也就是前文定义的 ActivityAttributes。第二个参数是个闭包,用于构建锁屏状态下的实时活动视图。第三个参数同样是个闭包,用于构建灵动岛的视图。
前面两个参数都比较简单,一看就会用,这里主要讲一讲灵动岛的实现。
灵动岛由 DynamicIsland 进行初始化,看一下初始化的方法签名:
1 | public init<Expanded, CompactLeading, CompactTrailing, Minimal>( expanded: @escaping () -> Expanded, compactLeading: @escaping () -> CompactLeading, compactTrailing: @escaping () -> CompactTrailing, minimal: @escaping () -> Minimal) where Expanded : DynamicIslandExpandedContent, CompactLeading : View, CompactTrailing : View, Minimal : View |
一共有四个闭包,分别是:
- Expanded,代表了灵动岛展开时的视图;
- CompactLeading,代表摄像头之前的视图;
- CompactTrailing,代表摄像头之后的视图;
- Minimal,存在多个实时活动时的视图;
后三个都是常见的 SwiftUI 视图,Expanded 中需要根据对应的区域来构建不同的视图。
如开发者官网文档的图片所示:
展开的视图分为四个区域,分别为 leading、trailing、center 和 bottom。所以在构建视图的时候根据需要显示的内容设置不同的视图:
1 | DynamicIslandExpandedRegion(.center) { |
上述代码实现了灵动岛展开情况下中间部分的视图,其余几个部分都大同小异。另外需要说明的是,当左右两边的空间不够时,可以使用 belowIfTooWide
修饰符将其内容显示在摄像头下方,但是这一条在实际使用时并没有生效,不知是使用方式的问题还是苹果的bug。
同样借用官网的图来表示左右两边的视图可以占据的最大空间。
4. 实时活动的创建、更新与结束
4.1 创建
为了创建实时活动,首先需要创建实时活动所需要的数据,注意,动态数据和静态数据要分别创建。
1 | let future = Calendar.current.date(byAdding: .minute, value: 20, to: Date())! |
然后使用 Activity 类就可以轻松地创建实时活动了。
1 | do { |
4.2 更新
更新则只需要创建一个新的动态数据,然后将其设置到实时活动就行了。
1 | var future = Calendar.current.date(byAdding: .minute, value: 10, to: Date())! |
需要注意的是,更新的同时,还会有一个提示,提示的声音既可以使用默认的,也可以自定义。
在后台更新可以参考使用 [[BackgroundTasks 实践#^a71227|BackgroundTasks]] 来做。
4.3 结束
结束就更简单了,需要传递两个参数。
第一个参数是动态数据,也就是实时活动结束时你想让它处于什么样的状态,那么这里就设置成什么样的数据。
第二个参数是结束时的策略,有三种可选:
- default,如果用户不手动清除屏幕上的实时活动,则系统会在四小时后自动清除;
- immediate,就是在活动结束时马上清除实时活动;
- after,需要提供一个日期,在这个时间到来时清除实时活动,此日期不能超过现在的四小时,超过的话,以四小时计。
1
await fitnessActivity?.end(using: fitnessState, dismissalPolicy: .default)
注意
需要注意的是,更新和结束都可以在后台执行,具体执行的方法参考Background Task
。
以上就是关于实时活动和灵动岛的全部内容了,后面再写利用远程通知来更新或结束实时活动的内容。
参考文章
官网文档