开发者社区 > 博文 > 带你走进灵动岛
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

带你走进灵动岛

  • y16879w
  • 2023-11-29
  • IP归属:北京
  • 14520浏览

    前言

    iOS最近几年新特性

    iOS14视频画中画
    AppLibrary
    桌面小组件
    照片隐私加强
    应用限免
    智能折叠
    全新siri悬浮显示
    iOS15
    FaceTime支持屏幕共享
    信息和新增拟我表情
    推出专注模式
    通知重新设计,图标变得更大
    地图公共交通路线置顶,增加时间显示
    识别图片上文字信息
    支持照片信息和照片上的文字进行搜索
    iOS16iOS 16 锁定界面
    锁定界面小组件
    锁屏界面的实时活动
    iPhone锁定全屏幕音乐播放器
    电池百分比出来啦
    视频实况文本
    快速查询Wi-Fi密码
    iOS17设置您的待机屏幕
    优先考虑交互式小部件
    定制您的联系海报
    创建您自己的贴纸
    设置新的 Safari 配置文件
    开启反追踪
    分享您的 iCloud 钥匙串密码

    一、简介

    实时活动(Live Activity),是iOS16新增的扩展组件功能,可以在灵动岛和锁定屏幕上显示应用程序的实时数据。用于追踪事件和任务进度实时活动的开始和结束都是离散的,具体画面场景如下:苹果

    苹果在 iPhone 14 Pro 及 iPhone 14 Pro MAX 上推出了灵动岛。灵动岛将 iPhone 前置镜头和软件通知结合在一起的全新设计,用出色的交互设计掩盖硬件的缺陷,是一次交互玩法的革新。灵动岛可以通过点按、长按、轻扫来进行交互,最多支持两个应用同时“登岛”。

    灵动岛全称 Dynamic Island,作为 iOS 中实时活动(Live Activities)功能的一部分,用来展示需要实时更新的消息。例如外卖配送信息,地图实时导航信息等。灵动岛有 3 种展现形式。

    1.1 展现形式

    1.1.1 紧凑(Compact)

    当系统只有 1 个实时活动的内容时,灵动岛默认使用紧凑模式。紧凑模式下UI由头部(Leading side)和尾部(Trailing side)组成,如图所示。用户可以点击灵动岛打开 App 查看实时活动的内容

    1.1.2 最小化(Minimal)

    当系统有多个实时活动的内容时,灵动岛自动切换使用最小化模式。最小化模式下由附着的头部(Leading(attached))和分割开的尾部(Trailing(detached))组成,如图所示。和紧凑模式一样,最小化模式也支持用户点击打开 App。

    1.1.3扩展(Expanded) 当用户在紧凑或最小化模式轻扫或长按灵动岛时,灵动岛可以切换成扩展模式。用于向用户展示更多信息。扩展模式的 UI


    设计尽量保持和紧凑模式一致,用户从紧凑模式切换到扩展模式会有一个平滑的体验。

    当我们向 App Store 提交了适配灵动岛的 App 版本时,以上 3 种模式都需要适配。

    二、场景限制

    2.1样式限制

    1、实时活动针对锁定屏幕和灵动岛提供了不同的视图。锁定屏幕可以出现在所有支持 iOS 16 的设备上。而灵动岛在支持设备上,使用以下视图显示实时活动:紧凑前视图、紧凑尾视图、最小视图和扩展视图。

    2、当用户触摸灵动岛,且灵动岛中有紧凑或最小视图,同时实时活动更新时,会出现扩展视图。在不支持灵动岛的设备上,扩展视图显示为实时活动更新的横幅。

    3、为确保系统可以在每个位置显示 App 的实时活动,开发者必须支持所有视图

    建议:同场景多卡片由于样式趋同且折叠,不建议同时创建多卡片

    灵动岛页面需要实现的部分有4个:

    a、不支持灵动岛的机型 或 锁屏时的 显示

    b、紧凑级展示(即左右贴合灵动岛的展示)

    c、多Live activity时的展示(即极小视图,左贴合,右分离)

    d、扩展视图(长按灵动岛时触发)

    备注:还有一个App同时存在的实时活动面板最多只能创建5个,这也是一个场景约束条件。Error requesting delivery Live Activity The operation couldn’t be completed. Maximum number of activities for target already exists

    2.2时间限制

    实时活动最多可以保持八小时的活动状态,除非其应用程序或人员在此限制之前结束活动。超过八小时限制后,系统自动结束直播活动,并立即将其移出动态岛。但是,实时活动会保留在锁定屏幕上,直到有人将其删除,或者在系统将其删除之前最多再保留四个小时(以先到者为准)。因此,实时活动在锁定屏幕上保留最多 12 小时。

    官方表述:https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities

    A Live Activity can be active for up to eight hours unless your app or a person ends it before this limit.  After the 8-hour limit, the system automatically ends it.  When a Live Activity ends, the system immediately removes it from the Dynamic Island.  However, the Live Activity remains on the Lock Screen until a person removes it or for up to four additional hours before the system removes it — whichever comes first.  As a result, a Live Activity remains on the Lock Screen for a maximum of twelve hours.

    2.3数据更新

    每个实时活动运行在自己的沙盒中,与小组件不同的是,它无法访问网络或接收位置更新。若要更新实时活动的动态数据,少量(不能超过4KB)数据可通过远程推送通知发送,或通过ActivityKit 框架后台活动刷新数据

    ActivityKit 更新和 ActivityKit 推送通知的更新动态数据大小不能超过 4 KB。

    2.4网络限制

    a、卡片本身禁止定位以及网络请求,数据刷新依赖本地刷新,实施活动推送刷新,同2)所述;

    b、Live Activity内部禁用网络图片,传统的服务端传图片URL的方式无法满足实际使用,但是希望传入订单图片来个性化地表达并且区分不同订单。

        iOS 16 beta版创建时可以通过将图片转为Data格式传入卡片,但是iOS16.1该方案仅限传入4KB左右的图片(API限制),因此暂时不考虑非本地图片方案,采用内置图片方式实现。


    2.5埋点限制

    场景情况:由于默认情况点击是回主程序,而并不是固定页面,因此有必要自定义widgetUrl(如用于回到订单页面),也可以通过Link实现分区域的跳转,Link和widgetUrl共存时,点击Link区域会响应Link,因此两者同时使用即可。

    无法在widget内部直接添加埋点,并且灵动岛收起时,仅支持添加同一个widgetUrl,对于收起状态添加Link并没有响应。

    埋点方式:因为点击直接跳转到主App,因此考虑将埋点参数加入URL参数即可,主App解析时埋点。但是无法记录包括用户查看、用户关闭(关闭卡片 继续发送推送也没有报错 因此无法判断)等行为的埋点。

    对于灵动岛的区分,实际测试发现,在展开模式下,可以加入Link并且可以正常响应,这与官方文档中的描述一致。

    三、适配

    3.1 UI适配

    1、尺寸

    目前只有 iPhone 14 Pro 及 iPhone 14 Pro MAX 具有灵动岛功能。在两种机型上,灵动岛的圆角半径都为 44Points,这个数值和前置深感摄像头的半径是一样的。按照前述的 3 种模式,灵动岛的具体参数如下表格所示(表格涉及的数值表示Points)。

    机型
    屏幕尺寸
    紧凑模式(头部)
    紧凑模式(尾部)
    最小化模式
    展开模式
    iPhone 14 Pro
    393*852
    52.33*36.67
    52.33*36.67
    36.67*36.67
    371*(84-160)
    iPhone 14 Pro Max
    430*932
    62.33*36.67
    62.33*36.67
    36.67*36.67
    408*(84-160)

    2、颜色

    开发者无法更改灵动岛的背景颜色,只能更改文字颜色、素材颜色、灵动岛边框颜色等。UI 适配需要考虑系统的深色模式,必要情况可以使用两套 UI。

    3.2开发适配

    3.2.1开发框架简介

    苹果在 iOS 16.1 正式对外开放了灵动岛适配框架ActivityKit,第三方 App 可以使用这些ActivityKit完成灵动岛适配工作。注意ActivityKit的 API 目前仅适用于 iPhone。灵动岛使用WidgetKitSwiftUI完成 UI 开发工作,ActivityKit在其中扮演创建Activity,请求数据,更新数据,结束Activity的角色。

    3.2.2权限管理

    灵动岛作为实时活动的一部分,需要实时活动权限才能正常展示。和通知权限,相机权限等类似,实时活动权限需要 App

    3.2.3生命周期

    Request

    Update

    Observe avtivity state

    End

    import ActivityKit
    
    struct AdventureAttributes: ActivityAttributes {
    //不可变
        let hero: EmojiRanger
        /// The associated type that describes the dynamic content of a Live Activity.
        ///
        /// The dynamic data of a Live Activity that's encoded by `ContentState` can't exceed 4KB.
        struct ContentState: Codable & Hashable {
            let currentHealthLevel: Double
            let eventDescription: String
        }
    }
    
    let adventure = AdventureAttributes(hero: hero)
    
    let initialState = AdventureAttributes.ContentState(
        currentHealthLevel: hero.healthLevel,
        eventDescription: "Adventure has begun!"
    )
    let content = ActivityContent(state: initialState, staleDate: nil, relevanceScore: 0.0)
    
    let activity = try Activity.request(
        attributes: adventure,
        content: content,
        pushType: nil
    )
    
    let heroName = activity.attributes.hero.name               
    let contentState = AdventureAttributes.ContentState(
        currentHealthLevel: hero.healthLevel,
        eventDescription: "\(heroName) has taken a critical hit!"
    )
    
    var alertConfig = AlertConfiguration(
        title: "\(heroName) has taken a critical hit!",
        body: "Open the app and use a potion to heal \(heroName)",
        sound: .default
    )  
         
    activity.update(
        ActivityContent<AdventureAttributes.ContentState>(
            state: contentState,
            staleDate: nil
        ),
        alertConfiguration: alertConfig
    )
    
    // Observe activity state asynchronously
    func observeActivity(activity: Activity<AdventureAttributes>) {
        Task {
            for await activityState in activity.activityStateUpdates {
                if activityState == .dismissed {
                    self.cleanUpDismissedActivity()
                }
            }
        }
    }
    
    // Observe activity state synchronously
    let activityState = activity.activityState
    if activityState == .dismissed {
        self.cleanUpDismissedActivity()
    }
    
    let hero = activity.attributes.hero
    
    let finalContent = AdventureAttributes.ContentState(
        currentHealthLevel: hero.healthLevel,
        eventDescription: "Adventure over! \(hero.name) has defeated the boss! Congrats!"
    )
    
    let dismissalPolicy: ActivityUIDismissalPolicy = .default
    
    activity.end(
        ActivityContent(state: finalContent, staleDate: nil),
        dismissalPolicy: dismissalPolicy)
    }
    
    3.2.4UI
    import WidgetKit
    import SwiftUI
    
    @main
    struct EmojiRangersWidgetBundle: WidgetBundle {
        var body: some Widget {
            EmojiRangerWidget()
            LeaderboardWidget()
            AdventureActivityConfiguration()
        }
    }
    
    struct AdventureActivityConfiguration: Widget {
        var body: some WidgetConfiguration {
            ActivityConfiguration(for: AdventureAttributes.self) { context in
                // ...
                // Create the view that appears on the Lock Screen and as a
                // banner on the Home Screen of devices that don't support the
                // Dynamic Island.
            } dynamicIsland: { context in
     // Create the views that appear in the Dynamic Island.
                DynamicIsland {
                    // Create the expanded view.
                    // Leading region
                    
    
                    // Expanded region
                    
    
                    // Bottom region
                     
                } compactLeading: {
                    // Create the compact leading view.
                    // ...
                } compactTrailing: {
                    // Create the compact trailing view.
                    // ...
                } minimal: {
                    // Create the minimal view.
                    // ...
                }
            }
        }
    }
    


    四、远程通知更新数据

    实时活动也支持远程推送更新,根据文档以下9点要求实现(avtivity远程推送每小时有通知预算(数量未明确),超出后系统将关闭通知)

    1、确保启动activity时[request(attributes:contentState:pushType:)传入pushType参数(.token);

    2、获取启动后的activity的推送令牌pushToken,传给服务端用来推送更新activity;(实时活动的pushToken不是消息通知的token,这个是独立出来的)

    3、服务端推送的更新内容字段需要和ActivityAttributes的ContentState中定义的动态数据字段对应;

    4、设置推送的报头apns-push-type的值为liveactivity;

    5、设置推送的报头apns-topic的值为.push-type.liveactivity;

    6、正确的推送对应的内容和状态;

    7、使用pushTokenUpdates监听pushToken变化,如有变化,就令牌失效,需要将新的令牌传给服务器;

    8、当Activity结束时,服务器端的pushToken将失效;

    {
        "aps": {
            "timestamp": 1685952000,
            "event": "update",
            "content-state": {
                "currentHealthLevel": 0.0,
                "eventDescription": "Power Panda has been knocked down!"
            },
            "alert": {
                "title": "Power Panda is knocked down!",
                "body": "Use a potion to heal Power Panda!",
                "sound": "default"
            }
        }
    }
    
    注意:

    1、不用为推送提供声音 , 如果推送延迟,在activity结束后收到时将被忽略,avtivity每小时有通知预算(数量未明确),超出后系统将关闭通知;

    2、实时活动的pushToken不是消息通知的token,这个token上报到JDPush服务,需要单独管理和归类。

    备注:

    1. 灵动岛的实时信息要有明确的开始和结束时间点
    2. 当一个实时信息持续超过 8 小时,系统会从灵动岛移除这个 App 的信息
    3. 当一个实时活动结束时,灵动岛上的展示信息也会立即被系统移除
    4. 避免在灵动岛上显示广告,毕竟引起用户反感可以被直接关闭
    5. App 要能够响应灵动岛的点击信息,跳转到 App 中的正确子页面,而不是停留在 App 的首页
    运用场景

    1、需在屏幕驻留的文字、图像为主的信息:如地图导航、airdrop 传输情况等;

    2、后台进行的音频类:如接电话、放音乐、录音、倒计时等;

    3、即时交互反馈:如充电、静音、人脸识别等。超过这三类信息后,桌面可能会变得杂乱无章

    参考文章

    ActivityKit官方文档

    https://developer.apple.com/videos/play/wwdc2023/10194

    https://developer.apple.com/videos/play/wwdc2023/10184

    https://www.jianshu.com/p/f410eba6c392

    https://www.bilibili.com/read/cv18549307/

    文章数
    1
    阅读量
    363

    作者其他文章