将@AppStoarge 与自定义对象数组一起使用不会持久化数据
Using @AppStoarge with a custom object array does not persist data
我是 SwiftUI 的新手,想使用 @AppStorage 属性 包装器以数组形式保存自定义 class 对象列表。我在这里找到了几篇文章,它们帮助我创建了以下通用扩展,我已将其添加到我的 AppDelegate 中:
extension Published where Value: Codable {
init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults? = nil) {
let _store: UserDefaults = store ?? .standard
if
let data = _store.data(forKey: key),
let value = try? JSONDecoder().decode(Value.self, from: data) {
self.init(initialValue: value)
} else {
self.init(initialValue: defaultValue)
}
projectedValue
.sink { newValue in
let data = try? JSONEncoder().encode(newValue)
_store.set(data, forKey: key)
}
.store(in: &cancellableSet)
}
}
这是我的 class 代表对象:
class Card: ObservableObject, Identifiable, Codable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
required init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CardKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
legality = try container.decode([String].self, forKey: .legality)
imageURL = try container.decode(String.self, forKey: .imageURL)
price = try container.decode(String.self, forKey: .price)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CardKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(description, forKey: .description)
try container.encode(imageURL, forKey: .imageURL)
try container.encode(price, forKey: .price)
}
init(id: Int, name: String, description: String, legality: [String], imageURL: String, price : String) {
self.id = id
self.name = name
self.description = description
self.legality = legality
self.imageURL = imageURL
self.price = price
}
}
enum CardKeys: CodingKey{
case id
case name
case description
case legality
case imageURL
case price
}
我正在使用一个视图模型 class,它声明数组如下:
@Published(wrappedValue: [], "saved_cards") var savedCards: [Card]
class 的其余部分仅包含将卡片附加到数组的函数,因此我认为没有必要在此处突出显示它们。
我的问题是,在应用程序运行期间,一切似乎都运行良好 - 卡片出现并在数组中可见,但是当我尝试关闭我的应用程序并再次重新打开它时,数组是空的,看起来数据没有持久化。看起来 JSONEncoder/Decoder 无法 serialize/deserialize 我的 class,但我不明白为什么。
非常感谢您的建议,因为我似乎找不到解决此问题的方法。我也对常规 Int 数组使用相同的方法,它可以完美地工作,所以我的自定义 class.
似乎有问题
通过使用 try? JSONDecoder().decode(Value.self, from: data)
而不是 do/try/catch
,您将错过 JSON 解码中发生的错误。编码器未输入 legality
密钥,因此解码失败。事实上,默认情况下,你所有的类型都在 Codable
上,所以如果你删除所有自定义 Codable encode/decode 并让编译器为你合成它,它 encodes/decodes 就好了。
@AppStorage 示例:
struct Card: Identifiable, Codable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
init(id: Int, name: String, description: String, legality: [String], imageURL: String, price : String) {
self.id = id
self.name = name
self.description = description
self.legality = legality
self.imageURL = imageURL
self.price = price
}
}
enum CardKeys: CodingKey{
case id
case name
case description
case legality
case imageURL
case price
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
do {
let result = try JSONDecoder().decode([Element].self, from: data)
print("Init from result: \(result)")
self = result
} catch {
print("Error: \(error)")
return nil
}
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
print("Returning \(result)")
return result
}
}
struct ContentView : View {
@AppStorage("saved_cards") var savedCards : [Card] = []
var body: some View {
VStack {
Button("add card") {
savedCards.append(Card(id: savedCards.count + 1, name: "\(Date())", description: "", legality: [], imageURL: "", price: ""))
}
List {
ForEach(savedCards, id: \.id) { card in
Text("\(card.name)")
}
}
}
}
}
使用@Published 查看模型版本(需要相同的 Card,以及上面的 Array 扩展):
class CardViewModel: ObservableObject {
@Published var savedCards : [Card] = Array<Card>(rawValue: UserDefaults.standard.string(forKey: "saved_cards") ?? "[]") ?? [] {
didSet {
UserDefaults.standard.setValue(savedCards.rawValue, forKey: "saved_cards")
}
}
}
struct ContentView : View {
@StateObject private var viewModel = CardViewModel()
var body: some View {
VStack {
Button("add card") {
viewModel.savedCards.append(Card(id: viewModel.savedCards.count + 1, name: "\(Date())", description: "", legality: [], imageURL: "", price: ""))
}
List {
ForEach(viewModel.savedCards, id: \.id) { card in
Text("\(card.name)")
}
}
}
}
}
您最初的 @Published 实现依赖于一个似乎不存在的 cancellableSet
,所以我将它换成了一个常规的 @Published 值,其中包含一个采用 UserDefaults 值的初始化程序,然后再次设置 UserDefaults在 didSet
我认为你可以像这样从你的模型中创建一个简单的结构:
struct Card : Codable , Identifiable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
enum CodingKeys: String, CodingKey {
case id
case name
case description
case legality
case imageURL
case price
}
}
然后使用 json编码器从您的数据中获取 json 字符串,如下所示
// Encode
let card = Card(id: 1, name: "AAA", description: "BBB" ....)
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(card)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
然后简单地使用 :
坚持下去
UserDefaults.standard.set(json, forKey: "yourData")
如果您想在UI中观察和使用它,您可以使用:
@AppStorage("yourData") var myArray: String = ""
我是 SwiftUI 的新手,想使用 @AppStorage 属性 包装器以数组形式保存自定义 class 对象列表。我在这里找到了几篇文章,它们帮助我创建了以下通用扩展,我已将其添加到我的 AppDelegate 中:
extension Published where Value: Codable {
init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults? = nil) {
let _store: UserDefaults = store ?? .standard
if
let data = _store.data(forKey: key),
let value = try? JSONDecoder().decode(Value.self, from: data) {
self.init(initialValue: value)
} else {
self.init(initialValue: defaultValue)
}
projectedValue
.sink { newValue in
let data = try? JSONEncoder().encode(newValue)
_store.set(data, forKey: key)
}
.store(in: &cancellableSet)
}
}
这是我的 class 代表对象:
class Card: ObservableObject, Identifiable, Codable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
required init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CardKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
legality = try container.decode([String].self, forKey: .legality)
imageURL = try container.decode(String.self, forKey: .imageURL)
price = try container.decode(String.self, forKey: .price)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CardKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(description, forKey: .description)
try container.encode(imageURL, forKey: .imageURL)
try container.encode(price, forKey: .price)
}
init(id: Int, name: String, description: String, legality: [String], imageURL: String, price : String) {
self.id = id
self.name = name
self.description = description
self.legality = legality
self.imageURL = imageURL
self.price = price
}
}
enum CardKeys: CodingKey{
case id
case name
case description
case legality
case imageURL
case price
}
我正在使用一个视图模型 class,它声明数组如下:
@Published(wrappedValue: [], "saved_cards") var savedCards: [Card]
class 的其余部分仅包含将卡片附加到数组的函数,因此我认为没有必要在此处突出显示它们。
我的问题是,在应用程序运行期间,一切似乎都运行良好 - 卡片出现并在数组中可见,但是当我尝试关闭我的应用程序并再次重新打开它时,数组是空的,看起来数据没有持久化。看起来 JSONEncoder/Decoder 无法 serialize/deserialize 我的 class,但我不明白为什么。
非常感谢您的建议,因为我似乎找不到解决此问题的方法。我也对常规 Int 数组使用相同的方法,它可以完美地工作,所以我的自定义 class.
似乎有问题通过使用 try? JSONDecoder().decode(Value.self, from: data)
而不是 do/try/catch
,您将错过 JSON 解码中发生的错误。编码器未输入 legality
密钥,因此解码失败。事实上,默认情况下,你所有的类型都在 Codable
上,所以如果你删除所有自定义 Codable encode/decode 并让编译器为你合成它,它 encodes/decodes 就好了。
@AppStorage 示例:
struct Card: Identifiable, Codable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
init(id: Int, name: String, description: String, legality: [String], imageURL: String, price : String) {
self.id = id
self.name = name
self.description = description
self.legality = legality
self.imageURL = imageURL
self.price = price
}
}
enum CardKeys: CodingKey{
case id
case name
case description
case legality
case imageURL
case price
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
do {
let result = try JSONDecoder().decode([Element].self, from: data)
print("Init from result: \(result)")
self = result
} catch {
print("Error: \(error)")
return nil
}
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
print("Returning \(result)")
return result
}
}
struct ContentView : View {
@AppStorage("saved_cards") var savedCards : [Card] = []
var body: some View {
VStack {
Button("add card") {
savedCards.append(Card(id: savedCards.count + 1, name: "\(Date())", description: "", legality: [], imageURL: "", price: ""))
}
List {
ForEach(savedCards, id: \.id) { card in
Text("\(card.name)")
}
}
}
}
}
使用@Published 查看模型版本(需要相同的 Card,以及上面的 Array 扩展):
class CardViewModel: ObservableObject {
@Published var savedCards : [Card] = Array<Card>(rawValue: UserDefaults.standard.string(forKey: "saved_cards") ?? "[]") ?? [] {
didSet {
UserDefaults.standard.setValue(savedCards.rawValue, forKey: "saved_cards")
}
}
}
struct ContentView : View {
@StateObject private var viewModel = CardViewModel()
var body: some View {
VStack {
Button("add card") {
viewModel.savedCards.append(Card(id: viewModel.savedCards.count + 1, name: "\(Date())", description: "", legality: [], imageURL: "", price: ""))
}
List {
ForEach(viewModel.savedCards, id: \.id) { card in
Text("\(card.name)")
}
}
}
}
}
您最初的 @Published 实现依赖于一个似乎不存在的 cancellableSet
,所以我将它换成了一个常规的 @Published 值,其中包含一个采用 UserDefaults 值的初始化程序,然后再次设置 UserDefaults在 didSet
我认为你可以像这样从你的模型中创建一个简单的结构:
struct Card : Codable , Identifiable{
let id : Int
let name : String
let description : String
let legality : [String]
let imageURL : String
let price : String
enum CodingKeys: String, CodingKey {
case id
case name
case description
case legality
case imageURL
case price
}
}
然后使用 json编码器从您的数据中获取 json 字符串,如下所示
// Encode
let card = Card(id: 1, name: "AAA", description: "BBB" ....)
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(card)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
然后简单地使用 :
坚持下去UserDefaults.standard.set(json, forKey: "yourData")
如果您想在UI中观察和使用它,您可以使用:
@AppStorage("yourData") var myArray: String = ""