You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 17.5模拟器Live Activity无法持续更新问题排查

Live Activity 在 iOS 17.5 模拟器异常,后台仅更新一次问题

问题现象

Live Activity 在 iOS 17.5 模拟器上无法正常工作,但物理设备运行正常。关闭应用后仅更新一次,之后不再触发更新。

已配置的签名与能力

  • Push notification
  • Background modes
  • Background fetch
  • Remote notification
  • Background processing

相关代码

struct OrderLiveActivityView: View {
    @State private var activity: Activity<OrderWidgetAttributes>? = nil
    @State private var progress: CGFloat = 0.33
    @State var activityTimer: Timer?

    var body: some View {
        VStack {
            Button {
                start()
            } label: {
                Text("Start")
                    .bold()
            }
            .padding()
            
            Button {
                removeLiveActivity()
            } label: {
                Text("Remove")
                    .bold()
            }
            .padding()
        }
        .padding()
    }
    
    @ViewBuilder
    var progressBarView: some View {
        VStack {
            HStack {
                ForEach(0..<4) { index in
                    Circle()
                        .fill(index <= Int(progress * 2) ? Color.green : Color.gray)
                        .frame(width: 20, height: 20)
                        .overlay(
                            Text("\(index + 1)")
                                .foregroundColor(.white)
                                .font(.caption)
                        )
                    if index < 3 {
                        Spacer()
                    }
                }
            }
            .padding(.top, 10)
            .padding(.horizontal, 10)
            
            ZStack {
                // Points above the progress bar
                HStack {
                    ForEach(0..<4) { index in
                        Circle()
                            .fill(Color.white)
                            .frame(width: 8, height: 8)
                        
                        if index < 3 {
                            Spacer()
                        }
                    }
                }
                
                // Custom Progress Bar
                ZStack {
                    Rectangle()
                        .fill(Color.white)
                        .frame(height: 2)
                        .cornerRadius(3)
                }
            }
            .padding(.horizontal)
            .padding(.bottom)
        }
        .background(Color.black.opacity(0.9))
        .cornerRadius(8)
    }
    
    func startStatusUpdateTimer() {
        activityTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { _ in
            updateOrderStatus()
        }
    }
    
    func stopStatusUpdateTimer() {
        // Invalidate the timer when no longer needed
        activityTimer?.invalidate()
        activityTimer = nil
    }
    
    func updateOrderStatus() {
        Task {
            if let activity = activity {
                let currentStatus = activity.content.state.status
                let newStatus = nextStatus(currentStatus)
                let newContentState = OrderWidgetAttributes.ContentState(
                    status: newStatus,
                    estimatedDeliveryTime: activity.content.state.estimatedDeliveryTime
                )
                
                let updatedContent = ActivityContent(state: newContentState, staleDate: nil)
                if #available(iOS 16.2, *) {
                    Task{
                        await activity.update(updatedContent, alertConfiguration: nil)
                    }
                } else  {
                    Task{
                        await  activity.update(using: newContentState)
                    }
                }
            } else {
                print("Not found activity)")
            }
        }
    }
    
    func nextStatus(_ currentStatus: OrderStatus) -> OrderStatus {
        let statuses = OrderStatus.allCases
        if let currentIndex = statuses.firstIndex(of: currentStatus) {
            let nextIndex = (currentIndex + 1) % statuses.count
            let status = statuses[nextIndex]
            if status == .ready {
                stopStatusUpdateTimer()
            }
            return status
        }
        return .ready
    }
    
    func start() {
        guard activity == nil else {return}
        let attr = OrderWidgetAttributes(orderID: UUID())
        let now = Date()
        
        // 10 saniye sonrası için tarihi hesapla
        let tenSecondsLater = now.addingTimeInterval(10)
        
        // ClosedRange<Date> oluştur
        let dateRange: ClosedRange<Date> = now...tenSecondsLater
        let state = OrderWidgetAttributes.ContentState(status: .inQueue, estimatedDeliveryTime: dateRange)
        let content = ActivityContent(state: state, staleDate: nil)
        do {
            
            self.activity = try Activity<OrderWidgetAttributes>.request(attributes: attr, content: content, pushType: .token)
            startStatusUpdateTimer()
            
            print("ok")
        } catch let error {
            print(error.localizedDescription)
        }
    }
    
    func removeLiveActivity() {
        // Ensure the activity exists
        guard let activity = activity else {
            print("No activity to remove.")
            return
        }
        
        Task {
            await activity.end(activity.content, dismissalPolicy: .immediate)
        }
        
        
    }
}

#Preview {
    OrderLiveActivityView()
}

疑问

已完成所有要求配置,但 Live Activity 仍无法更新,请问可能遗漏了什么?


问题排查与解决方案

1. 模拟器本身的限制

iOS 17.5 模拟器在 Live Activity 后台更新上存在已知行为差异:应用退到后台后,基于 RunLoop 的定时器会被系统挂起,无法重复触发。物理设备后台执行机制更宽松,所以能正常运行。

2. 定时器后台失效问题

你用的 Timer.scheduledTimer 依赖主线程 RunLoop,应用进入后台后 RunLoop 休眠,定时器直接停摆,这就是后台仅更新一次的核心原因。

解决办法:

  • 改用 BGTaskScheduler 后台任务,申请后台执行时间触发更新;
  • 用 远程推送更新 Live Activity(已配置 Push 能力),这是苹果推荐的稳定方案,不受后台定时器限制。

3. staleDate 配置缺失

你设置了 staleDate: nil,系统会默认认为内容无需持续维护,可能限制更新频率。建议设置合理的过期时间,比如当前时间后3小时:

// 修改 start() 方法中的内容
let staleDate = Date().addingTimeInterval(3 * 60 * 60)
let content = ActivityContent(state: state, staleDate: staleDate)

4. 后台模式细节检查

确认 Background processing 已勾选,同时检查 Info.plist 中 UIBackgroundModes 数组是否包含 processing、fetch、remote-notification 三个键值。

5. Activity 实例持有问题

应用后台时,@State private var activity 可能被系统释放,导致后续更新找不到实例。建议把 Activity ID 存到 UserDefaults,更新时通过 ID 重新获取:

// 启动时存储 ID
self.activity = try Activity<OrderWidgetAttributes>.request(attributes: attr, content: content, pushType: .token)
UserDefaults.standard.set(activity?.id.uuidString, forKey: "CurrentLiveActivityID")

// 更新时获取实例
func updateOrderStatus() {
    Task {
        guard let activityID = UserDefaults.standard.string(forKey: "CurrentLiveActivityID"),
              let activity = Activity<OrderWidgetAttributes>.activities.first(where: { $0.id.uuidString == activityID }) else {
            print("Activity not found")
            return
        }
        // 原有更新逻辑不变
    }
}

6. iOS 17 后台更新限制

iOS 17 对 Live Activity 本地触发的更新频率有严格管控,后台一段时间后会完全停止定时器。苹果明确推荐用远程推送或后台任务来实现后台更新,不要依赖本地定时器。


内容的提问来源于stack exchange,提问作者Ömer faruk Bozbulut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:14:51