可在@AppStoradge或@SceneStorage中编码

问题描述 投票:0回答:0

我想将

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
的正确方法是什么?

swiftui codable
© www.soinside.com 2019 - 2024. All rights reserved.