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
相关产品推荐
相关产品推荐

