使用 struct id 将收藏夹保存到 UserDefaults

Saving favorites to UserDefaults using struct id

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

我很确定我遗漏了什么……

这是我的数据结构和 class:

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)
                    }
                }
            }
        }
    }
}

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

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. 在保存时,我保存了两个 Set——收藏夹 城市
  4. 在原来的 ForEach 中,我遍历了所有 cities,然后只标记属于 favorites
  5. 的部分

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