写点什么

在 iOS 应用中使用实时活动与灵动岛

作者:珲少
  • 2023-12-30
    上海
  • 本文字数:3700 字

    阅读完需:约 12 分钟

在 iOS 应用中使用实时活动与灵动岛

iOS16 系统引入了实时活动与灵动岛相关的 API。实时活动 API 能够让用户在桌面直接浏览到应用程序所提供的实时性较高的信息,例如比赛的比分信息,外卖的配送进度信息,票务信息等。在支持灵动岛的设备上,实时活动配合灵动岛,更是能带给用户沉浸式的信息获取体验,在某些特定应用场景下非常有用。

1 - 引言

从 iOS16 开始,实时活动能够在锁屏、待机桌面以及灵动岛等位置提供信息更新展示。在某些特定场景下,实时活动可以提供给用户几个小时内掌握实时事件、活动或任务更新。常见的应用场景有:


- 外卖类应用实时提供用户配送进度,剩余时间。


- 赛事类应用的实时分数。


- 健身类应用与可穿戴设备的实时体能状态更新。


实时应用将会展示在设备的:


- 锁屏页面


- 通知列表顶部


- 在支持灵动岛的设备上,在灵动岛位置展示


- 不支持灵动岛的设备上,实时活动的更新会在屏幕顶部弹出通知


- 待机显示时,实时活动会充满整个屏幕


需要注意,灵动岛的可可显示区域优先,在开发实时活动时,在设计上可以参考下面的最佳实践文档:


https://developer.apple.com/cn/design/human-interface-guidelines/live-activities/

2 - 开发实时活动与适配灵动岛

首先,要支持实时活动需要在工程中创建一个 Widget Extension,实时活动本身也是小组件的一种。如下图所示:



需要注意,在创建 Widget 小组件时,将 Include Live Activicy 选中,如下图:



虽然实时活动也是小组件,但其他普通的 Widget 区别很大,小组件是通过 Timeline 来进行更新,而实时活动只能通过 App 触发更新或特殊 Push 更新和关闭;小组件需要用户手动添加使用,而实时活动则是由主 App 进行开启。创建好 Tatget 后,模版自带 3 个文件,其中 LiveWidgetLiveActivity 中实时活动的核心框架代码,我们做些简单的修改,如下:


import ActivityKitimport WidgetKitimport SwiftUI// 状态结构体struct LiveWidgetAttributes: ActivityAttributes {    public struct ContentState: Codable, Hashable {        // 动态的状态,有App或Push来触发更新        var score: String    }
// 静态的状态,实时活动开启时指定 var name: String var teamA: String var teamB: String}
// 编写SwiftUI页面struct LiveWidgetLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: LiveWidgetAttributes.self) { context in // Lock screen/banner UI goes here VStack { Text(context.attributes.name) Text("💻💻💻💻💻💻💻💻💻💻💻") Text("\(context.attributes.teamA) 对战 \(context.attributes.teamB)") Text("当前比分:\(context.state.score)") }.padding(.all, 10) .activityBackgroundTint(Color.cyan) .activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in DynamicIsland { // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { Text(context.attributes.teamA) } DynamicIslandExpandedRegion(.trailing) { Text(context.attributes.teamB) } DynamicIslandExpandedRegion(.center) { Text("\(context.attributes.name)") } DynamicIslandExpandedRegion(.bottom) { Text("当前比分:\(context.state.score)") } } compactLeading: { Text(context.attributes.teamA) } compactTrailing: { Text(context.attributes.teamB) } minimal: { Text("🆚") } .widgetURL(URL(string: "http://www.apple.com")) .keylineTint(Color.red) } }}
复制代码


上面代码中,LiveWidgetAttributes 定义实时活动所需要的数据模型,其中直接定义的属性可以理解为静态的属性,即当实时活动开启时就确定的数据,例如比赛参与的队伍,外卖的订单信息等。其中的 ContentState 用来定义需要更新的数据,例如比分数据,外卖剩余时间数据等。


实时活动只能使用 SwiftUI 来编写,如上代码所示 ActivityConfiguration 配置实时活动组件,dynamicIsland 参数用来对灵动岛进行适配。


在进行灵动岛的适配时,需要对灵动岛的各种状态进行配置:


1 - 展开状态下的灵动岛。


2 - 长条状态下的灵动岛。


3 - mini 状态下的灵动岛。


展开状态下的灵动岛分为左右中下 4 部分,如上代码所示可以对每个部分进行布局,在开启实时活动时,长按灵动岛可以进入展开状态,效果如下图所示:



长条状态是灵动岛的默认状态,可以对左右两部分进行配置,效果如下图:



当同时有多个 App 开启了实时活动时,灵动岛上将只展示一个圆圈,此时即是 mini 状态,通常可以配置为一个图标,如下图所示:



在锁屏或拉下通知栏时,实时活动将展示在所有通知的最上方,如下图所示:



在 iOS17 中,当设备在横屏充电时,会自动进入待机状态,如果有实时活动,实时活动将占据整个待机页面,如下图:


3 - 实时活动的开启与更新

前面有提到过,实时活动只能通过主 App 来开启,LiveWidgetLiveActivity 结构体需要在主 App 中被引入。首先设置 LiveWidgetLiveActivity 的 Target Membership 为主 App 与小组件 Target 共享,如下图:



在主 App 中调用如下代码来进行实时活动的开启:


import UIKit// 框架引入import ActivityKitclass ViewController: UIViewController {    override func viewDidLoad() {        super.viewDidLoad()        // Do any additional setup after loading the view.        let attri = LiveWidgetAttributes(name: "XX杯英雄电竞总决赛", teamA: "王族队", teamB: "斩星队")        let state = LiveWidgetAttributes.ContentState(score: "2-1")        do {            try Activity.request(attributes: attri, contentState: state, pushType: .token) }        catch {            print(error)        }           }}
复制代码


Activicy.request 方法来请求开启一个实时活动,Activicy 中还有一些方法用来对实时活动进行更新和获取更新实时活动的 PushToken,下面是其中封装的核心属性方法的详细解释:


public class Activity<Attributes> : Identifiable where Attributes : ActivityAttributes {    // 开启一个实时活动,其中PushType目前只支持Token模型的Push更新    public static func request(attributes: Attributes, content: ActivityContent<Activity<Attributes>.ContentState>, pushType: PushType? = nil) throws -> Activity<Attributes>    // 获取应用当前的实时活动    public static var activities: [Activity<Attributes>] { get }    // 异步队列,用来监听实时活动的更新    public static var activityUpdates: Activity<Attributes>.ActivityUpdates { get }    // 当前实时活动的状态: 活跃,已结束,已移除    public var activityState: ActivityState { get }    // 用来监听状态变更    public var activityStateUpdates: Activity<Attributes>.ActivityStateUpdates { get }    // 通过Push更新实时活动时的PushToken    public var pushToken: Data? { get }    // 异步的对实时活动状态进行更新    public func update(using contentState: Activity<Attributes>.ContentState) async    public func update(_ content: ActivityContent<Activity<Attributes>.ContentState>) async    public func update(using contentState: Activity<Attributes>.ContentState, alertConfiguration: AlertConfiguration? = nil) async    public func update(_ content: ActivityContent<Activity<Attributes>.ContentState>, alertConfiguration: AlertConfiguration? = nil) async    // 结束一个实时活动    public func end(using contentState: Activity<Attributes>.ContentState? = nil, dismissalPolicy: ActivityUIDismissalPolicy = .default) async    public func end(_ content: ActivityContent<Activity<Attributes>.ContentState>?, dismissalPolicy: ActivityUIDismissalPolicy = .default) async}
复制代码


最后,需要注意,此处进行更新实时活动的 Push 必须是基于 Token 验证的 APNs,不能是基于证书验证的 APNs,此处的 Token 不是从 Application 接口拿到的 Device Token,而是从 Apple Developer 后台创建的鉴权 Token,另外,基于 Token 的 Push 认证要比基于证书的更加方便,且无需关心过期时间,如果你的应用的 APNs 目前依然是基于证书的,则需要进行改造后才能使用其来更新实时活动。

发布于: 刚刚阅读数: 4
用户头像

珲少

关注

还未添加个人签名 2022-07-26 加入

还未添加个人简介

评论

发布
暂无评论
在iOS应用中使用实时活动与灵动岛_珲少_InfoQ写作社区