svc*_*cho 3 class swift swiftui appstorage
我对 SwiftUI 相当陌生,想要使用 @AppStorage 属性包装器以数组的形式保存自定义类对象的列表。我在这里找到了几篇文章,帮助我创建了以下通用扩展,我已将其添加到我的 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)
}
}
Run Code Online (Sandbox Code Playgroud)
这是我代表该对象的类:
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
}
Run Code Online (Sandbox Code Playgroud)
我正在使用一个视图模型类,它声明数组如下:
@Published(wrappedValue: [], "saved_cards") var savedCards: [Card]
Run Code Online (Sandbox Code Playgroud)
该类的其余部分仅包含将卡片附加到数组的函数,因此我认为没有必要在这里突出显示它们。
我的问题是,在应用程序运行时,一切似乎都工作正常 - 卡片出现并在数组中可见,但是当我尝试关闭我的应用程序并再次重新打开它时,数组为空,并且数据似乎没有持久化。看起来 JSONEncoder/Decoder 无法序列化/反序列化我的类,但我不明白为什么。
我真的很感激建议,因为我似乎找不到解决这个问题的方法。我还对常规 Int 数组使用相同的方法,该数组工作完美,因此看来我的自定义类存在问题。
By using try? JSONDecoder().decode(Value.self, from: data)
and not do/try/catch
you're missing the error that's happening in the JSON decoding. The encoder isn't putting in the legality
key, so the decoding is failing to work. In fact, all of your types on Codable
by default, so if you remove all of your custom Codable encode/decode and let the compiler synthesize it for you, it encodes/decodes just fine.
Example with @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)")
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
View model version with @Published (requires same Card, and Array extension from above):
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)")
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Your original @Published implementation relied on a cancellableSet
that didn't seem to exist, so I switched it out for a regular @Published value with an initializer that takes the UserDefaults value and then sets UserDefaults again on didSet
归档时间: |
|
查看次数: |
3119 次 |
最近记录: |