iOS架构

iOS-Live Activity开发

2023-08-13  本文已影响0人  梵生

文档

官方文档
官方文档-用户交互指南
官方文档-构建推送消息
主要参考文档
参考文档

开发须知

开发流程

// 入口代码,确定需要加载 Live Activity
@main
struct PizzaDeliveryWidgets: WidgetBundle {
    var body: some Widget {
        // widget
        FavoritePizzaWidget()
        // Live Activity
        if #available(iOS 16.1, *) {
            PizzaDeliveryLiveActivity()
        }
    }
}
// 配置数据
struct PizzaDeliveryAttributes: ActivityAttributes {
    public typealias PizzaDeliveryStatus = ContentState

    // 动态数据
    public struct ContentState: Codable, Hashable {
        var driverName: String
        var deliveryTimer: ClosedRange<Date>
    }
    
    // 静态数据
    var numberOfPizzas: Int
    var totalAmount: String
    var orderNumber: String
}
// 配置UI,共计需要设计出 4 个 UI,且全都需要实现
struct PizzaDeliveryActivityWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
            // 锁屏UI,出现在所有设备上 - 第一个UI
            // 不支持灵动岛的未锁屏的设备上,显示为 banner UI
            // 两个都使用同一个View组件,在这里配置
            // 系统使用默认的文本颜色和最适合锁定屏幕的实时活动背景色
            VStack {
                Text("Your \(context.state.driverName) is on the way!")
            }
            .activityBackgroundTint(Color.cyan)  // 修改背景颜色
            .activitySystemActionForegroundColor(Color.black)  //  修改文本颜色
        } dynamicIsland: { context in
            // 灵动岛UI
            DynamicIsland {
                // 展开后的UI - 第二个UI
                // 需要组合不同的区域
                DynamicIslandExpandedRegion(.leading) {
                    // 展开后的前面
                    Text("Leading")
                }
                DynamicIslandExpandedRegion(.trailing) {
                    // 展开后的后面
                    Text("Trailing")
                }
                DynamicIslandExpandedRegion(.center) {
                    // 展开后的中间
                    Text("Center")
                }
                DynamicIslandExpandedRegion(.bottom) {
                    // 展开后的底部
                    Text("Bottom")
                }
            } compactLeading: {
                // 紧凑样式的左边 - 第三个UI(左)
                Text("L")
            } compactTrailing: {
                // 紧凑样式的右边 - 第三个UI(右)
                Text("T")
            } minimal: {
                // 当有多个 Live Activity时,灵动岛显示成circular minimal 样式 - 第四个UI
                Text("Min")
            }
            .widgetURL(URL(string: "demo://homepage"))  // 点击跳转到指定页面
            .keylineTint(Color.red)
        }
    }
} 
    // 启动 Live Activity
    func startDeliveryPizza() {
        // 判断版本号
        guard #available(iOS 16.1, *) else {
            return
        }
        // 判断是否开启了 Live Activity 权限
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            // Live Activity 不可用,上报空 token 给服务端
            uploadTokenToService(nil)
            return
        }
        
        // 创建数据
        let pizzaDeliveryAttributes = PizzaDeliveryAttributes(numberOfPizzas: 1, totalAmount:"$99")
        let initialContentState = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "TIM 👨🏻🍳", estimatedDeliveryTime: Date()...Date().addingTimeInterval(15 * 60))
                                                  
        do {
            // 请求启动 Live Activity
            let deliveryActivity = try Activity<PizzaDeliveryAttributes>.request(
                attributes: pizzaDeliveryAttributes,
                contentState: initialContentState,
                pushType: .token)   // Enable Push Notification Capability First (from pushType: nil)
            
            print("Requested a pizza delivery Live Activity \(deliveryActivity.id)")

            Task {
                // 监听 push token 更新
                for await pushToken in deliveryActivity.pushTokenUpdates {
                    let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) }
                    print(pushTokenString)
                    // 上传 push token 给服务端,用于推送更新 Live Activity
                    uploadTokenToService(pushTokenString)
                }
            }
            Task {
                // 监听 state 数据内容变化
                for await state in deliveryActivity.contentStateUpdates {
                    print("1content state update: tip=\(state.driverName)")
                }
            }
            Task {
                // 监听 Activity 状态变化
                for await state in deliveryActivity.activityStateUpdates {
                    print("activity state update: tip=\(state) id:\(deliveryActivity.id)")
                    // 当 LiveActivity 结束时,使服务端的推送token失效
                    // LiveActivity 活动状态一共有 4 种
                    // .active 处于活动中
                    // .ended 已经终止且不会有任何更新,但依旧在锁屏界面展示
                    // .dismissed 结束且不再展示
                    // .stale 消息过时,等待最新的消息。(iOS 16.2 以上才支持)
                    if state == .ended || state == .dismissed {
                        uploadTokenToService(nil)
                    }
                }
                
            }
        } catch (let error) {
            print("Error requesting pizza delivery Live Activity \(error.localizedDescription)")
            // Live Activity 不可用,上报空 token 给服务端
            uploadTokenToService(nil)
        }
    }
    // 更新 Live Activity
    func updateDeliveryPizza() {
        // 判断版本号
        guard #available(iOS 16.1, *) else {
            return
        }
        Task {
            // 获取数据
            let updatedDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "TIM 👨🏻🍳", estimatedDeliveryTime: Date()...Date().addingTimeInterval(60 * 60))
            
            // 更新数据
            for activity in Activity<PizzaDeliveryAttributes>.activities{
                // 用户可以在锁定屏幕上移除Live Activity 后,ActivityState会变为.dismissed。
                if activity.activityState == .dismissed {
                    continue
                }
                await activity.update(using: updatedDeliveryStatus)
            }
        }
    }
    // 结束 Live Activity
    func stopDeliveryPizza() {
        // 判断版本号
        guard #available(iOS 16.1, *) else {
            return
        }
        Task {
            // dismissalPolicy 有三种
            // .default 会在锁屏屏幕上停留四个小时,以便用户查看最后一个消息,或用户主动移除
            // .immediate 立即结束,不会在屏幕上停留
            // .after() 指定时间结束,最长为当前时间+4小时
            for activity in Activity<PizzaDeliveryAttributes>.activities{
                // 用户可以在锁定屏幕上移除Live Activity 后,ActivityState会变为.dismissed。
                if activity.activityState == .dismissed {
                    continue
                }
                await activity.end(dismissalPolicy: .immediate)
            }

            print("Cancelled pizza delivery Live Activity")
        }
    }
// 展示所有 Live Activity
func showAllDeliveries() {
        // 判断版本号
        guard #available(iOS 16.1, *) else {
            return
        }
        Task {
            for activity in Activity<PizzaDeliveryAttributes>.activities {
                print("Pizza delivery details: \(activity.id) -> \(activity.attributes)")
            }
        }
    }

推送更新数据

// 官方提供的推送消息内容示例
{
    "aps": {
        "timestamp": 1168364460,
        "events": "update",
        "relevance-score": 75.0,
        "stale-date": 1650998941,
        "content-state": {
            "driverName": "Anne Johnson",
            "estimatedDeliveryTime": 1659416400
        },
        "alert": {
            "title": "Delivery Update",
            "body": "Your pizza order will arrive soon.",
            "sound": "example.aiff" 
        }
    }
}

常见问题

The operation couldn’t be completed. (com.apple.ActivityKit.ActivityInput error 1.)
// 检查主工程的info里面,是否添加了NSSupportsLiveActivities并设置为YES
The operation couldn’t be completed. (com.apple.ActivityKit.ActivityInput error 0.)
// 检查主工程,是否添加了推送功能。Project - Target - Signing & Capabilities,如果没有 Push Notificatioins,则点击 + 添加
上一篇下一篇

猜你喜欢

热点阅读