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

SwiftUI秒表应用关闭重启后状态持久化问题求助

SwiftUI秒表应用持久化问题
  • 启动活动计时后,关闭再重启应用,秒表无法继续计时,预期后台仍持续记录时长
  • 重启应用后,当前活动及已归档活动的记录时间全部重置为0,需要持久化两类活动的时间数据

附相关代码:

struct Activity: Identifiable, Codable, Equatable {
    ...
    var startTime: Date? // 记录活动开始时间
    ...
    init(from decoder: Decoder) throws {
        ...
        let startTimeKey = "startTime\(id.uuidString)"
        startTime = UserDefaults.standard.object(forKey: startTimeKey) as? Date // 从UserDefaults加载startTime
    }

    func encode(to encoder: Encoder) throws {
        ...
        let startTimeKey = "startTime\(id.uuidString)"
        UserDefaults.standard.set(startTime, forKey: startTimeKey) // 将startTime保存到UserDefaults
    }
    ...
}

class ViewModel: ObservableObject {
    ...
    func startActivity(_ index: Int) {
        // 记录活动开始时间
        activities[index].startTime = Date()
        activities[index].isPlaying = true
        saveActivities()
    }

    func stopActivity(_ index: Int) {
        // 清空活动开始时间
        activities[index].startTime = nil
        activities[index].isPlaying = false
        saveActivities()
    }

    func loadActivities() {
        activities = store.load()
        // 处理应用关闭时正在运行的活动,计算已流逝时间
        for index in activities.indices where activities[index].isPlaying {
            guard let startTime = activities[index].startTime else { continue }
            let timeElapsed = Date().timeIntervalSince(startTime)
            activities[index].currentTime += timeElapsed
            activities[index].startTime = Date() // 重置startTime为当前时间
        }
        saveActivities()
    }
    ...
}

问题分析与修复方案

核心问题1:startTime的持久化逻辑错误

你在Codable的init和encode方法里直接操作UserDefaults,但Codable协议的方法不应该用来做额外的存储操作——当编码/解码Activity数组时,每个Activity都会读写UserDefaults,但这会导致:

  • 解码时,id还没完成初始化就被用来生成startTimeKey,此时id可能是默认值,导致读取错误的键
  • 编码时,UserDefaults的操作和数组的持久化不同步,容易出现数据不一致

修复:把startTime纳入Codable编码,不再单独存UserDefaults

修改Activity结构,让startTime成为Codable的一部分,去掉自定义的init(from:)和encode(to:):

struct Activity: Identifiable, Codable, Equatable {
    let id = UUID()
    var isPlaying: Bool
    var currentTime: TimeInterval
    var startTime: Date? // 该属性会被自动编码/解码
    
    // 移除自定义的init(from:)和encode(to:),使用默认实现
}

核心问题2:loadActivities的时间计算逻辑错误

当前代码在加载活动时,累加currentTime后直接重置startTime并保存,但如果store.load()读取的startTime是旧值(因为之前没正确持久化),就会导致时间计算错误。另外,归档活动的currentTime没有被正确持久化——需要确保currentTime在每次停止活动或应用退出时都被正确保存。

修复:修正加载时的时间计算逻辑

func loadActivities() {
    activities = store.load()
    let now = Date()
    for index in activities.indices where activities[index].isPlaying {
        guard let startTime = activities[index].startTime else { 
            // 若startTime丢失,标记活动为停止状态
            activities[index].isPlaying = false
            continue 
        }
        // 计算从上次启动到现在的流逝时间,累加到currentTime
        let elapsed = now.timeIntervalSince(startTime)
        activities[index].currentTime += elapsed
        // 更新startTime为当前时间,保证后续计时连续
        activities[index].startTime = now
    }
    saveActivities()
}

核心问题3:应用退出时未处理正在运行的活动

当应用被强制关闭时,saveActivities()可能没被调用,导致startTime和currentTime的最新状态丢失。需要在应用进入后台时自动保存状态。

修复:监听应用生命周期事件,后台时保存状态

在ViewModel中添加生命周期监听:

class ViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 监听应用进入后台事件
        NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
            .sink { [weak self] _ in
                self?.saveActivities()
            }
            .store(in: &cancellables)
    }
    
    // ... 其他原有代码 ...
}

额外建议:确保store的持久化逻辑正确

你的store.load()和saveActivities()需要正确处理Activity数组的编码/解码,以下是基于UserDefaults的实现示例:

// 保存活动数组
func saveActivities() {
    guard let data = try? JSONEncoder().encode(activities) else { return }
    UserDefaults.standard.set(data, forKey: "SavedActivities")
}

// 加载活动数组
func loadActivities() {
    if let data = UserDefaults.standard.data(forKey: "SavedActivities"),
       let savedActivities = try? JSONDecoder().decode([Activity].self, from: data) {
        activities = savedActivities
    }
    // ... 后续时间计算逻辑 ...
}

内容的提问来源于stack exchange,提问作者Nenharmha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 20:50:26