【问题标题】:Saving favorites to UserDefaults using struct id使用 struct id 将收藏夹保存到 UserDefaults
【发布时间】:2026-01-15 08:05:02
【问题描述】:

我正在尝试将用户最喜欢的城市保存在 UserDefaults 中。发现此解决方案保存了结构 ID - 构建并运行但似乎没有保存:在应用重新启动时,先前点击的按钮被重置。

我很确定我错过了什么……

这是我的数据结构和类:

struct City: Codable {
    var id = UUID().uuidString
    var name: String
}

class Favorites: ObservableObject {
    private var cities: Set<String>
    let defaults = UserDefaults.standard

    var items: [City] = [
        City(name: "London"),
        City(name: "Paris"),
        City(name: "Berlin")
    ]

    init() {
        let decoder = PropertyListDecoder()
        if let data = defaults.data(forKey: "Favorites") {
            let cityData = try? decoder.decode(Set<String>.self, from: data)
            self.cities = cityData ?? []
            return
        } else {
            self.cities = []
        }
    }

    func getTaskIds() -> Set<String> {
        return self.cities
    }

    func contains(_ city: City) -> Bool {
        cities.contains(city.id)
    }

    func add(_ city: City) {
        objectWillChange.send()
        cities.contains(city.id)
        save()
    }

    func remove(_ city: City) {
        objectWillChange.send()
        cities.remove(city.id)
        save()
    }

    func save() {
        let encoder = PropertyListEncoder()
        if let encoded = try? encoder.encode(tasks) {
            defaults.setValue(encoded, forKey: "Favorites")
        }
    }
}

这里是 TestDataView

struct TestData: View {

    @StateObject var favorites = Favorites()
    
    var body: some View {
        ForEach(self.favorites.items, id: \.id) { item in
            VStack {
                Text(item.title)
          
                Button(action: {
                    if self.favorites.contains(item) {
                        self.favorites.remove(item)
                    } else {
                        self.favorites.add(item)
                    }
                }) {
                    HStack {
                        Image(systemName: self.favorites.contains(item) ? "heart.fill" : "heart")
                            .foregroundColor(self.favorites.contains(item) ? .red : .white)
                    }
                }
            }
        }
    }
}

【问题讨论】:

    标签: swiftui userdefaults


    【解决方案1】:

    有几个问题,我将在下面解决。这是工作代码:

    struct ContentView: View {
    
        @StateObject var favorites = Favorites()
        
        var body: some View {
            VStack(spacing: 10) {
                ForEach(Array(self.favorites.cities), id: \.id) { item in
                    VStack {
                        Text(item.name)
                  
                        Button(action: {
                            if self.favorites.contains(item) {
                                self.favorites.remove(item)
                            } else {
                                self.favorites.add(item)
                            }
                        }) {
                            HStack {
                                Image(systemName: self.favorites.contains(item) ? "heart.fill" : "heart")
                                    .foregroundColor(self.favorites.contains(item) ? .red : .black)
                            }
                        }
                    }
                }
            }
            
        }
    }
    
    struct City: Codable, Hashable {
        var id = UUID().uuidString
        var name: String
    }
    
    class Favorites: ObservableObject {
        @Published var cities: Set<City> = []
        @Published var favorites: Set<String> = []
        let defaults = UserDefaults.standard
    
        var initialItems: [City] = [
            City(name: "London"),
            City(name: "Paris"),
            City(name: "Berlin")
        ]
    
        init() {
            let decoder = PropertyListDecoder()
            
            if let data = defaults.data(forKey: "Cities") {
                cities = (try? decoder.decode(Set<City>.self, from: data)) ?? Set(initialItems)
            } else {
                cities = Set(initialItems)
            }
            self.favorites = Set(defaults.array(forKey: "Favorites") as? [String] ?? [])
        }
    
        func getTaskIds() -> Set<String> {
            return self.favorites
        }
    
        func contains(_ city: City) -> Bool {
            favorites.contains(city.id)
        }
    
        func add(_ city: City) {
            favorites.insert(city.id)
            save()
        }
    
        func remove(_ city: City) {
            favorites.remove(city.id)
            save()
        }
    
        func save() {
            let encoder = PropertyListEncoder()
            if let encoded = try? encoder.encode(self.cities) {
                self.defaults.set(encoded, forKey: "Cities")
            }
            self.defaults.set(Array(self.favorites), forKey: "Favorites")
            defaults.synchronize()
        }
    }
    

    原版问题:

    1. 最大的问题是items 在每次新发布时都会重新创建,City 有一个id,在创建时分配了一个UUID。这保证了每次新的发布,每批城市都会有不同的UUIDs,所以储蓄的情况永远不会奏效。
    2. 存在一些常见的拼写错误和对实际不存在的属性的引用。

    我做了什么:

    1. citiesfavorites 都设为@Published 属性,这样您就不必手动调用objectWillChange.send
    2. 在初始化时,加载城市收藏夹。这样一来,城市在最初创建后将始终具有相同的 UUID,因为它们是从保存状态加载的
    3. 保存时,我同时保存Sets -- 收藏夹城市
    4. 在原始ForEach 中,我遍历所有cities,然后只标记属于favorites 的部分

    重要提示:在测试时,我发现至少在 Xcode 12.3 / iOS 14.3 上,同步到 UserDefaults,即使使用 now-不推荐的synchronize 方法。我一直想知道为什么当我杀死然后重新打开应用程序时我的更改没有反映出来。最终发现如果我给它大约 10 到 15 秒的时间来同步到 UserDefaults 然后关闭应用程序然后再次打开它,一切都会正常工作。

    【讨论】:

    • 感谢您的回复和详细的解释!奇迹般有效。我对错别字不利,因为它是一个更大项目的一部分,我将其分解并更改了名称以使其更简单。我知道问题出在哪里,现在一切都说得通了。非常感谢!
    最近更新 更多