用Live Activity替代推送通知实现计时交互的技术问题
用Live Activity替代本地通知实现倒计时交互方案
核心思路
Live Activity本身没有内置的调度触发API,所以需要保留本地通知的调度逻辑,让原通知触发时间点:
- 播放提示音
- 自动启动Live Activity展示1分钟倒计时
- 处理Live Activity的点击(1分钟内点击→Success)和超时(1分钟未点击→Failed)逻辑
一、修正基础代码错误
1. 修复Activity Attributes语法错误
原代码中typealias语法错误,修正后:
struct TimeActivityAttributes: ActivityAttributes { // 定义动态状态类型 public struct TimerStatus: Codable, Hashable { var endTime: Date } // 指定ContentState关联类型为TimerStatus public typealias ContentState = TimerStatus // 静态属性 var name: String }
2. 修复Live Activity启动方法的类型错误
原代码中类型不匹配(TimerActivityAttributes应为TimeActivityAttributes),且本地场景无需pushType: .token(该参数用于远程推送更新Live Activity):
var activity: Activity<TimeActivityAttributes>? = nil func startActivity(withEndTime endTime: Date) { let attributes = TimeActivityAttributes(name: "Timer") let state = TimeActivityAttributes.TimerStatus(endTime: endTime) // 本地启动无需token,pushType设为nil即可 activity = try? Activity<TimeActivityAttributes>.request(attributes: attributes, contentState: state, pushType: nil) }
二、调整本地通知配置(触发Live Activity+仅提示音)
1. 修正重复添加通知的错误
原调度代码中重复调用center.add(request),删除冗余逻辑:
let numberOfNotifications = 24 let intervalBetweenNotifications = timeRange / TimeInterval(numberOfNotifications) for i in 1...numberOfNotifications { let randomTimeInterval = TimeInterval.random(in: 0...intervalBetweenNotifications) let notificationDate = startDate.addingTimeInterval(TimeInterval(i) * intervalBetweenNotifications + randomTimeInterval) let dateComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: notificationDate) let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) // 配置通知:仅播放提示音,不显示弹窗 let content = UNMutableNotificationContent() content.sound = UNNotificationSound.default // 提示音 content.userInfo = ["notificationFiredAt": notificationDate] // 保留触发时间用于后续校验 let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) center.add(request) { error in if let error = error { debugPrint(error) } } }
2. 通知触发时启动Live Activity
实现UNUserNotificationCenterDelegate的willPresent方法,在通知触发时启动Live Activity并仅播放提示音:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // 仅播放提示音,不显示通知弹窗 completionHandler([.sound]) // 启动1分钟倒计时的Live Activity let endTime = Date().addingTimeInterval(60) startActivity(withEndTime: endTime) }
三、处理Live Activity的交互与超时逻辑
1. 点击Live Activity触发Success
在Live Activity的UI视图中添加点击手势,点击后结束Activity并打印结果:
// 在Live Activity的View结构体中添加 struct TimeActivityView: View { let attributes: TimeActivityAttributes @ActivityState var state: TimeActivityAttributes.TimerStatus var body: some View { let timeRemaining = max(state.endTime.timeIntervalSinceNow, 0) Text("剩余时间: \(Int(timeRemaining))秒") .onTapGesture { // 结束Activity并打印Success Task { await Activity<TimeActivityAttributes>.activities.first?.end(dismissalPolicy: .immediate) print("Success") } } } }
2. 超时自动触发Failed
在Live Activity的更新逻辑中,监听endTime,当时间到达时自动结束Activity并打印结果:
// 在Live Activity的View结构体中 struct TimeActivityView: View { let attributes: TimeActivityAttributes @ActivityState var state: TimeActivityAttributes.TimerStatus var body: some View { let timeRemaining = max(state.endTime.timeIntervalSinceNow, 0) Text("剩余时间: \(Int(timeRemaining))秒") .onChange(of: timeRemaining) { newValue in if newValue <= 0 { // 超时,结束Activity并打印Failed Task { await Activity<TimeActivityAttributes>.activities.first?.end(dismissalPolicy: .immediate) print("Failed") } } } } }
关键说明
pushType: .token仅用于远程推送更新Live Activity,本地场景无需配置,设为nil即可正常启动- 若需确保Live Activity在后台也能正常显示,需在
Info.plist中添加NSSupportsLiveActivities并设为YES - 重复触发通知时,可先检查是否已有活跃的Live Activity,避免重复启动(可在
startActivity方法中添加判断)
内容的提问来源于stack exchange,提问作者Mel
相关产品推荐
相关产品推荐

