使用Swift Data与Dictionary时界面无法自动更新的问题咨询
问题描述
为避免中等数据量下列表/LazyStack卡顿,我给SwiftData的@Model类Pet设置了一个以年月维度的DateComponents为键、Feed数组为值的字典属性feeds。但由于Feed需要实现Codable,无法标记为@Model,导致修改数据后UI无法自动刷新,甚至修改设备日期也不会触发更新,仅首次加载数据正常。
相关代码如下:
Pet类代码
@Model class Pet: Identifiable { // 主模型类 @Attribute(.unique) var id = UUID() // 其他属性... @Relationship(deleteRule: .cascade) var feeds: [DateComponents:[Feed]] // 其他代码... }
Feed类代码
class Feed: Codable, Identifiable, Hashable, Observable { static func == (lhs: Feed, rhs: Feed) -> Bool { lhs.startDate == rhs.startDate && lhs.id == rhs.id && ... } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(startDate) ... } var id = UUID() var startDate: Date // 其他属性... var latest: Bool { self.dateComponents == Calendar.current.dateComponents([.year, .month, .day], from: Date()) } }
Feed_View代码
struct Feed_View: View { @Environment(\.modelContext) private var modelContext @Bindable var pet: Pet var feedsByDate:[Feed]{ (pet.feeds[Calendar.current.dateComponents([.year, .month], from:Date())] ?? []).sorted(by: {$0.startDate > $1.startDate}) } var body: some View { NavigationStack { // 界面内容... ForEach(feedsByDate, id: \.id) { feed in Row(feed: feed, pet: pet) } // 其他界面代码... } } }
解决方案
1. 让Feed同时兼容SwiftData与Codable
SwiftData的@Model类可以实现Codable,无需放弃@Model标记。只需手动实现编码解码逻辑,同时满足SwiftData的初始化要求:
@Model class Feed: Codable, Identifiable, Hashable { static func == (lhs: Feed, rhs: Feed) -> Bool { lhs.startDate == rhs.startDate && lhs.id == rhs.id && ... } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(startDate) ... } @Attribute(.unique) var id = UUID() var startDate: Date // 其他属性... // Codable编码解码逻辑 enum CodingKeys: String, CodingKey { case id, startDate // 列出所有需要编码的属性 // 其他属性key... } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(startDate, forKey: .startDate) // 编码其他属性... } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) startDate = try container.decode(Date.self, forKey: .startDate) // 解码其他属性... } // SwiftData要求的空初始化器 init() { id = UUID() startDate = Date() // 其他属性设置默认值... } var latest: Bool { self.dateComponents == Calendar.current.dateComponents([.year, .month, .day], from: Date()) } }
2. 重构存储结构,使用SwiftData原生关系
字典作为SwiftData属性无法自动触发更新,建议将Pet的feeds改为[Feed]数组,在View中动态分组:
@Model class Pet: Identifiable { @Attribute(.unique) var id = UUID() // 其他属性... @Relationship(deleteRule: .cascade) var feeds: [Feed] = [] // 其他代码... }
在Feed_View中实现分组与日期监听:
struct Feed_View: View { @Environment(\.modelContext) private var modelContext @Bindable var pet: Pet private let currentMonth = Calendar.current.dateComponents([.year, .month], from: Date()) var feedsByDate: [Feed] { pet.feeds.filter { feed in let feedMonth = Calendar.current.dateComponents([.year, .month], from: feed.startDate) return feedMonth == currentMonth }.sorted(by: {$0.startDate > $1.startDate}) } var body: some View { NavigationStack { List(feedsByDate) { feed in Row(feed: feed, pet: pet) } // 其他界面代码... } // 监听设备日期变化,触发视图刷新 .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in objectWillChange.send() } } }
3. 手动触发UI刷新(临时方案)
若暂时不想修改存储结构,可在修改Feed后手动发送对象变化通知:
- 在编辑Feed的视图中,保存修改时调用:
// 假设编辑视图持有@Bindable var feed: Feed和@Bindable var pet: Pet func saveChanges() { // 修改feed属性逻辑... pet.objectWillChange.send() // 手动触发Pet的变化通知,让Feed_View刷新 }
- 在Feed_View中添加日期监听:
.onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in pet.objectWillChange.send() }
内容的提问来源于stack exchange,提问作者Fran
相关产品推荐
相关产品推荐

