我想将
Codable
存储在@AppStoradge 和@SceneStorage 中。看起来就像扩展一样简单RawRepresentable
:
import Foundation
// This fails with `@AppStorage`
extension RawRepresentable where Self: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Self.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
// Causes error: BAD_EXEC
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
然后我尝试以下代码:
import SwiftUI
struct Credentials: Codable {
var userName: String = ""
var password: String = ""
}
extension Credentials: RawRepresentable { }
struct Recipe: Identifiable {
let id: UUID
let name: String
let instructions: String
}
typealias PinnedRecipes = [UUID]
extension PinnedRecipes: RawRepresentable { }
struct ContentView: View {
@AppStorage("pinnedRecipes") var pinnedRecipes = PinnedRecipes()
@AppStorage("credentials") var credentials = Credentials()
@State private var newCredentials = Credentials()
// MARK: - View state
@State private var isShowingCredentials = false
let recipes = [
Recipe(
id: UUID(uuidString: "F4BD08ED-56FD-4343-BD66-78B2A2932CC8")!,
name: "Chocolate Cake", instructions: "Make a chocolate cake."
),
Recipe(
id: UUID(uuidString: "5B326712-968E-4032-87A0-F44060A411F7")!,
name: "Banana Smoothie", instructions: "Make a banana smoothie."
),
Recipe(
id: UUID(uuidString: "856A059C-22D1-4BCC-BF93-F9C5D98EA5AE")!,
name: "Pizza", instructions: "Make a pizza."
),
Recipe(
id: UUID(uuidString: "6149F636-80B3-44E9-9F4B-77E6B4586720")!,
name: "Apple Crumble", instructions: "Make an Apple Crumble."
),
Recipe(
id: UUID(uuidString: "232B6A49-59C8-4CC9-A990-AA8E49E668FF")!,
name: "Scones", instructions: "Make scones."
),
Recipe(
id: UUID(uuidString: "6545164C-D9A3-445F-BBC6-B4F92073531A")!,
name: "Potato Gratin", instructions: "Make potato gratin."
),
Recipe(
id: UUID(uuidString: "970AAF64-F895-4038-B604-2F4BD0FFA6B8")!,
name: "Sandwiches", instructions: "Make sandwiches."
)
]
var body: some View {
NavigationView {
List {
Section(header: Text("Pinned Recipes")) {
ForEach(pinnedRecipes, id: \.self) { id in
if let recipe = recipes.first(where: {$0.id == id}) {
PinnedRecipeRow(recipe: recipe, pinnedRecipes: $pinnedRecipes)
}
}
if pinnedRecipes.count == 0 {
Text("No pinned recipes yet.")
.animation(nil)
.foregroundColor(.secondary)
}
}
Section(header: Text("My Recipes")) {
ForEach(recipes.filter({ !pinnedRecipes.contains($0.id) })) { recipe in
RecipeRow(recipe: recipe, pinnedRecipes: $pinnedRecipes)
}
}
}
.navigationTitle("Recipes")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
isShowingCredentials = true
} label: {
Text("Credentials")
}
.buttonStyle(.bordered)
}
}
.sheet(isPresented: $isShowingCredentials) {
Section("Credentials") {
TextField("UserName", text: $newCredentials.userName)
TextField("Password", text: $newCredentials.password)
HStack {
Button {
credentials = newCredentials
isShowingCredentials = false
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
Button(role: .cancel) {
isShowingCredentials = false
} label: {
Label("Cancel", systemImage: "arrow.down.right.and.arrow.up.left")
}
}
}
.padding()
.presentationDetents([.medium])
}
.onAppear {
newCredentials = credentials
if credentials.userName.isEmpty || credentials.password.isEmpty {
isShowingCredentials = true
}
}
}
}
}
struct PinnedRecipeRow: View {
let recipe: Recipe
@Binding var pinnedRecipes: [UUID]
var body: some View {
NavigationLink(destination: Text(recipe.instructions)) {
HStack {
Text(recipe.name)
Spacer()
Image(systemName: "pin.fill")
.padding()
.onTapGesture {
withAnimation {
if let indexToRemove = pinnedRecipes.firstIndex(of: recipe.id) {
pinnedRecipes.remove(at: indexToRemove)
}
}
}
}
}
}
}
struct RecipeRow: View {
let recipe: Recipe
@Binding var pinnedRecipes: [UUID]
var body: some View {
NavigationLink(destination: Text(recipe.instructions)) {
HStack {
Text(recipe.name)
Spacer()
if pinnedRecipes.count < 3 {
Image(systemName: "pin")
.padding()
.onTapGesture {
withAnimation {
pinnedRecipes.append(recipe.id)
}
}
}
}
}
.transition(.scale)
}
}
注意: 保存对于
PinnedRecipes: RawRepresentable { }
可以正常工作,但对于 Credentials: RawRepresentable { }
则失败。
我曾经将
Codable
保存到 UserDefaults 中,没有任何问题,但我想知道在@AppStoradge 或 @SceneStorage 等 SwiftUI 状态包装器中存储 Codable
的正确方法是什么?