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

SwiftUI如何实现存储自定义对象数组的字典驱动的ForEach自动更新

实现方案

1. 自定义类型协议适配

首先确保你的Coupon类型遵循必要协议,满足ForEach唯一标识、比较判断、UserDefaults序列化的要求:

// 建议用struct实现值类型,属性变更会自动触发集合变更通知
struct Coupon: Identifiable, Equatable, Codable {
    let id = UUID() // 保证全局唯一,适配ForEach的id要求
    var date: Date
    var data: [String]
    // 其余自定义属性
}

2. 封装可观测的数据管理类

用ObservableObject封装字典数据,通过@Published修饰字典实现变更自动通知,同时内置UserDefaults的读写同步逻辑:

import Combine
import SwiftUI

class CouponManager: ObservableObject {
    static let shared = CouponManager()
    // @Published会在属性变更时自动通知所有订阅的视图刷新
    @Published var couponDict: [String: [Coupon]] = [:]
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 初始化加载本地存储数据
        loadFromUserDefaults()
        // 监听UserDefaults对应key的变更,自动同步到内存
        UserDefaults.standard.publisher(for: \.coupons)
            .sink { [weak self] _ in
                self?.loadFromUserDefaults()
            }
            .store(in: &cancellables)
    }
    
    private func loadFromUserDefaults() {
        guard let data = UserDefaults.standard.data(forKey: "coupons"),
              let decodedDict = try? JSONDecoder().decode([String: [Coupon]].self, from: data) else {
            couponDict = [:]
            return
        }
        couponDict = decodedDict
    }
    
    // 对外提供存储方法,修改数据后调用即可同步到UserDefaults
    func saveToUserDefaults() {
        guard let encodedData = try? JSONEncoder().encode(couponDict) else { return }
        UserDefaults.standard.set(encodedData, forKey: "coupons")
    }
}

3. 视图层绑定实现自动刷新

视图中用@StateObject持有管理类实例,直接绑定动态字典数据到ForEach即可:

struct CouponListView: View {
    // iOS14及以上用@StateObject,iOS13改用@ObservedObject
    @StateObject private var couponManager = CouponManager.shared
    @State var currentview = ""
    
    var body: some View {
        List {
            // 直接取字典的动态key,避免静态key无法响应字典键的增删变更
            ForEach(couponManager.couponDict.keys.sorted(), id: \.self) { key in
                Section(header: Text(key)) {
                    // Coupon已遵循Identifiable,无需额外指定id参数
                    ForEach(couponManager.couponDict[key] ?? []) { coupon in
                        Button(ifexpiredString(date: coupon.date)+coupon.data[1]) {
                            let coupons = UserDefaults.standard.coupons
                            let couponIndex = coupons.firstIndex(of: coupon)
                            self.currentview = "Coupon"+String(couponIndex!)
                        }
                        .foregroundColor(ifexpired(date: coupon.date) ? Color.gray : ifexpireing(date: coupon.date))
                    }
                }
            }
        }
    }
    
    // 你的工具方法定义
    func ifexpiredString(date: Date) -> String { /* 实现逻辑 */ }
    func ifexpired(date: Date) -> Bool { /* 实现逻辑 */ }
    func ifexpireing(date: Date) -> Color { /* 实现逻辑 */ }
}

注意事项

  • 如果你必须用class类型定义Coupon而非struct,需要额外让Coupon遵循ObservableObject,并给需要修改的属性加上@Published修饰,否则单个Coupon的属性变更不会触发字典的变更通知,视图不会刷新。
  • 所有优惠券数据的增删改操作,要么直接修改CouponManager.shared.couponDict,要么修改UserDefaults后调用loadFromUserDefaults同步,都会自动触发List刷新。
  • 不要用静态的keys数组遍历,直接读取字典的动态keys,才能响应字典新增、删除键的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:00:05