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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:05:36