1. 前语

实时活动是iOS 16.1及以上版别中新增的功用,它允许应用在锁屏界面显现实时数据,能够协助用户实时检查当时订单的进展,而无需解锁手机。用户在货拉拉APP上下单后,能够将手机放置一旁,开端其他作业。当用户想要查询订单状况时,只需从确定屏幕或灵动岛上轻松操作即可。实时活动的出现不只省去了用户解锁手机的过程,更为用户节省了时刻和精力。现在货拉拉APP适配“灵动岛”的最新6.7.68版别已正式上线,欢迎大家晋级体会。在适配过程中,货拉拉App也踩过很多“坑”,在此汇总为实战经验分享给大家。

2. Live Activity&灵动岛的介绍

Live Activity的完结需求运用Apple的ActivityKit框架。经过运用ActivityKit,开发者能够轻松地创立一个Live Activity,这是一个动态的、实时更新的活动,能够在用户的设备上显现各种信息。此外,ActivityKit还供给了推送告诉的功用,开发者能够经过服务器向用户的设备发送更新;这样,即使应用程序没有运转,用户也能够接收到最新的信息。

灵动岛是Live Activity的一种展现方法,灵动岛有三种展现方法:Compact紧凑、Minimal最小化,Expanded扩展。开发时有必要完结这三种方法,以确保灵动岛在不同的场景下都能正常展现。

货拉拉用户 iOS 端灵动岛实践总结
货拉拉用户 iOS 端灵动岛实践总结

一起还需求完结锁屏下的实时活动UI,设备处于锁屏状况下,也能检查实时更新的内容。以上功用的完结,都是运用WidgetKit和SwiftUI完结开发。

2.1 技能难点及战略

实时活动,主要是APP在后台时,自动更新告诉栏和灵动岛的数据,为用户展现最新实时订单状况。怎么及时改写实时活动的数据,是一个重点、难点。

更新方法有3种:

  1. 经过APP内订单状况的改变改写实时活动和灵动岛。此办法开发量小,可是APP退到后台30s后或许进程杀掉,会停止数据的更新。
  2. 让APP装备支撑后台运转形式,经过本地现有的订单状况改变逻辑,在后台发起网络恳求,获取订单的数据后改写实时活动。此办法开发量小,但求主App进程有必要存在,进程一旦杀掉就无法更新。
  3. 经过接受长途推送告诉来更新实时活动。此办法需求后端配合,此方法比较灵敏,无需App进程存在,数据更新及时。也是业界常见的方案。

经过对数据改写的三种方案进行评价后,选择了用户体会最佳的第三种方法。经过后端产生push,端上接受push数据来更新实时活动。

3. Live Activity&灵动岛的实践

3.1 完结方案流程图

完结流程图:

货拉拉用户 iOS 端灵动岛实践总结

3.2 完结代码

创立Live Activities的预备:

  • Xcode需求14.1以上版别
  • 在主工程的 Info.plist 文件中增加一个键值对,key 为 NSSupportsLiveActivities,value 为 YES
  • 运用ActivityKit在Widget Extension 中创立一个Live Activity

需求完结锁屏状况下UI、灵动岛长按打开的UI、灵动岛单个UI、多个实时活动时的minimalUI

import SwiftUI
import WidgetKit
@main
struct TestWidget: Widget {    
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TestAttributes.self) { context in
            // 锁屏状况下的UI
        } dynamicIsland: { context in
            DynamicIsland {
                //灵动岛打开后的UI
            } compactLeading: {
                // 未被打开左边UI
            } compactTrailing: {
                // 未被打开右边UI
            } minimal: {
               // 多任务时,右边的一个圆圈区域
            }
            .keylineTint(.cyan)
        }
    }
}

灵动岛主要分为StartUpdateEnd三种状况,可由ActivityKit长途推送操控其状况。

敞开Live Activity

        let state = TestAttributes.ContentState()
        let attri = TestAttributes(value: 100)
        do {
            let current = try Activity.request(attributes: attri, contentState: state, pushType: .token)
            Task {
                for await state in current.contentStateUpdates {
                    //监听state状况
                }
            }
            Task {
                for await state in current.activityStateUpdates {
                 //监听activity状况
                }
            }
        } catch(let error) {
        }

更新Live Activity

   Task {
            guard let current = Activity<TestAttributes>.activities.first else {
                return
            }
            let state = TestAttributes.ContentState(value: 88)
            await current.update(using: state)
        }

完毕Live Activity

    Task {
            for activity in Activity<TestAttributes>.activities {
              await activity.end(dismissalPolicy: .immediate)
            }
        }

4. 运用ActivityKit推送告诉

ActivityKit供给了接收推送令牌的功用,咱们能够运用这个令牌来经过ActivityKit推送告诉从咱们的服务器向Apple Push Notification service (APNs)发送更新。

推送更新Live Activity的预备:

  • 在开发者后台装备生成p8证书,替换原来的p12证书

  • 经过pushTokenUpdates获取推送令牌PushToken

  • 向后端注册PushToken

代码展现:

//取得PushToken
for await tokenData in current.pushTokenUpdates {
    let mytoken = tokenData.map { String(format: "%02x", $0) }.joined()
    //向后端注册
    registerActivityToken(mytoken)
}

4.1 模拟器push验证测验

环境要求:

Xcode >= 14.1 MacOS >= 13.0

预备作业:

  1. 经过pushTokenUpdates获取推送需求的token
  2. 依据开发者TeamID、p8证书本地途径、BuidleID等进行脚本装备

脚本示例:

export TEAM_ID=YOUR_TEAM_ID
export TOKEN_KEY_FILE_NAME=YOUR_AUTHKEY_FILE.p8
export AUTH_KEY_ID=YOUR_AUTHKEY_ID
export DEVICE_TOKEN=YOUR_PUSH_TOKEN
export APNS_HOST_NAME=api.sandbox.push.apple.com
export JWT_ISSUE_TIME=$(date +%s)
export JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
export JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
curl -v 
--header "apns-topic:YOUR_BUNDLE_ID.push-type.liveactivity" 
--header "apns-push-type:liveactivity" 
--header "authorization: bearer $AUTHENTICATION_TOKEN" 
--data 
'{"Simulator Target Bundle": "YOUR_BUNDLE_ID",
"aps": {
  "timestamp":1689648272,
   "dismissal-date":0,
   "event": "update",
    "sound":"default",
   "content-state": {
      "title": "等候付款",
      "content": "请赶快完结下单"
   }
}}' 
--http2 
https://${APNS_HOST_NAME}/3/device/$DEVICE_TOKEN

其间:

apns-topic:固定为{BundleId}.push-type.liveactivity

apns-push-type:固定为liveactivity

Simulator Target Bundle:模拟器推送,设置为对应APP的BundleId

timestamp:表明推送告诉的发送时刻,假如timestamp字段的值与当时时刻相差太大,可能会收不到推送。

event:可填入update、end,对应Live Activity的更新与完毕。

dismissal-date:当event为end时有用,表明完毕后从锁屏上移除Live Activity的时刻。假如推送内容不包括”dismissal-date”,默认完毕后4小时后消失,但内容不会再产生更新。假如期望Live Activity完毕后当即从锁屏上移除它,可为”dismissal-date”供给一个曩昔的日期。

content-state:对应灵动岛的Activity.ContentState;假如push中content-state的字段和Attributes比较:

  • 字段过多,多余的字段可能会被疏忽,不会导致解析失利

  • 字段短少,会在解析push告诉时出现问题过错。过错表现为:实时活动会有蒙层,并展现loading菊花UI。

演示:

货拉拉用户 iOS 端灵动岛实践总结

货拉拉用户 iOS 端灵动岛实践总结

5. 踩坑记录

  • 在模拟器上无法获取到pushToken,无法进行推送模拟?

    检查电脑的体系版别号,需求13.0以上

  • 更新实时活动时,页面显现加载loadingUI,为什么?

    核对push字段和Activity.ContentState的字段是否完全一致,字段少了会解析失利

  • 在16.1体系上,无法展现实时活动,其他更高体系能展现?

    检查Widget里边iOS体系版别号的装备,设置为想要支撑的最低版别

  • dismissal-date设置为10分钟后才消失,为什么Dynamic Island灵动岛当即消失了?

    Dynamic Island的显现逻辑可能会愈加复杂,假如push的event=end,Dynamic Island灵动岛会当即消失。期望一起消失,能够在指定时刻再发end,dismissal-date设置为曩昔时刻,锁屏UI和Dynamic Island灵动岛会一起消失。

  • 推送不期望打扰用户,静默推送,不需求轰动和自动弹出,怎么设置?

    将”content-available”设置为1,”sound” 设置为: “”

"aps" = {
        "content-available" : 1,
        "sound" : ""
    }
  • 用户体系是深色形式时,怎么适配?

    能够运用@Environment(.colorScheme)属性包装器来获取当时设备的颜色形式。会返回一个ColorScheme枚举,它能够是.light.dark。在依据详细的场景进行UI适配

struct ContentView: View {
    @Environment(.colorScheme) var colorScheme
    var body: some View {
        VStack {
            if colorScheme == .dark {
                Text("深夜形式")
                    .foregroundColor(.white)
                    .background(Color.black)
            } else {
                Text("日间形式")
                    .foregroundColor(.(.black)
                    .background(Color.white)
            }
        }
    }
}

5.1 场景约束及建议

  1. 官方文档提示实时活动最多持续8小时,8小时后数据无法改写,12小时后会强制消失。因而8小时后的数据不精确
  2. 实时活动的卡片上制止定位以及网络恳求,数据需求小于4KB,不能展现特别担任巨大的数据
  3. 同场景多卡片因为款式趋同且折叠,不建议一起创立多卡片。用户多次下单时,建议只处理第一个订单

6. 用户APP上线作用

用户端iOS APP灵动岛上线后的部分场景截图:

货拉拉用户 iOS 端灵动岛实践总结
货拉拉用户 iOS 端灵动岛实践总结
货拉拉用户 iOS 端灵动岛实践总结
货拉拉用户 iOS 端灵动岛实践总结
货拉拉用户 iOS 端灵动岛实践总结

7. 总结

灵动岛功用自上线以来,经过咱们的数据统计,用户实时活动运用率高达75%以上。这一数据的背后,是灵动岛强大的功用和优异的用户体会。用户能够在锁屏页直接检查订单状况,无需繁琐的操作过程,大大提升了用户体会。这种快捷性,使得灵动岛在用户中的接受度较高。

咱们的方案不只能够应用于当时的事务场景,后续还方案扩展到营销活动,定制化告诉消息等多种事务场景。这种扩展性,使得灵动岛能够更好地满足不同用户的需求,丰厚产品运营战略。

咱们期望经过分享开发过程中遇到的问题和解决方案,能够协助到更多的人。假如你有任何问题或许想法,欢迎在评论区留言。等待咱们在技能的道路上再次相遇。

总的来说,灵动岛以其高效、快捷、灵敏的特性,赢得了用户的广泛好评。咱们将继续尽力,为用户供给更优质的服务,为产品的发展注入更多的活力。