SwiftUI、AppStorage 和在 TabViews 中使用 Picker

SwiftUI, AppStorage and using Picker in TabViews

我正在尝试使用 AppStorage 跨多个视图使用选取器保存少量数据。但是,我 运行 遇到的问题是,当我 select 一个值和 link 到 AppStorage 时,它​​会更改所有其他值。我想要的是在多个视图中保存每个 selection 的值。如果我使用 @State 变量,selections 工作正常,但当我关闭并重新打开应用程序时,这些值不会被保存。我很确定我需要将每个 selection 发送到它自己的 @AppStorage 变量,但我很难弄清楚如何做到这一点。

struct Animals: Identifiable {
    var id = UUID().uuidString
    var name: String
    var animalTypes: [AnimalTypes]
}
var allAnimals = [
    Animals(name: "fred", animalTypes: [.shiba, .lab]),
    Animals(name: "barney", animalTypes: [.shiba, .dobberman, .lab]),
    Animals(name: "molly", animalTypes: [.snowshoe, .burmese, .bengal]),
    Animals(name: "bob", animalTypes: [.burmese]),
    Animals(name: "wilma", animalTypes: [.snowshoe, .bengal]),
]

enum AnimalTypes: String, CaseIterable, Codable {
    // Dog Breeds
    case shiba, lab, dobberman
    // Cat Breeds
    case snowshoe, burmese, bengal
}

struct AnimalsView: View {

    
    @State var animals: Animals!
    
    var body: some View {
        TabView {
            ForEach(allAnimals) { animal in
                AnimalSelectionView(animals: animal)
                .tag(animal.name)
            }
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    }
}

struct AnimalSelectionView: View {
    
    @AppStorage("selection") var animalSelection: Int = 0 // Saves the same value across all pickers (how to save individual values?)
//    @State private var animalSelection: Int = 0 // Sets value for each picker in each tabview, but doesn't save
    @State var animals: Animals!
    
    var body: some View {
        VStack {
            if animals.animalTypes.count <= 1 {
                Text("\(animals.animalTypes[0].rawValue)")
            } else {
                Text("\(animals.animalTypes[animalSelection].rawValue)")
            }
            
            if animals.animalTypes.count > 1 {
                Picker(selection: $animalSelection, label: Text("Select Animal Type")) {
                    ForEach(0 ..< animals.animalTypes.count, id:\.self) { item in
                        Text("\(item + 1)")
                            .font(.caption)
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
                .frame(width: 100, height: 17)
            }
        }
    }
}

我看到您已决定在您的 Animals class 上创建一个名为 id 的 属性。令人高兴的是,这是为每个 Animals 对象保存唯一 UserDefaults 值的完美工具。

你可以这样做:

struct AnimalSelectionView: View {

    @AppStorage var animalSelection: Int
    @State var animals: Animals

    init(animals: Animals) {
        self.animals = animals
        // The below line simply assigns the key which should be used to access your
        // desired variable. This way, it can be keyed specifically to your `Animals`
        // id value.
        self._animalSelection = AppStorage(wrappedValue: 0, "selection-\(animals.id)")
    }

    ...

}

请记住,如果您实例化两个在初始化中传递了相同参数的不同对象,您将无法检索到相同的值。

示例:

let fred = Animals(name: "fred", animalTypes: [.shiba, .lab])
let fred2 = Animals(name: "fred", animalTypes: [.shiba, .lab])

为清楚起见编辑:

这里,fredfred2大部分相同,但是id 属性对于这些实例中的每一个Animals 个会有所不同。因此,如果您尝试使用另一个的 id 访问一个的选择值,您将不会收到正确的值。您必须保留确切的 Animals 实例才能访问存储在 UserDefaults 中的值,或者至少根据需要保留其 id。此规则适用于所有应用程序会话。如果您实例化一个 Animals 对象然后退出并重新启动应用程序,当同一个 Animals 对象是 re-instantiated 时,它将根据您的定义分配一个不同的 idAnimals.id = UUID().uuidString.

为了在应用程序会话中保留它们的 id,您可以将动物存储在 CoreDataUserDefaults 中,授予它们静态 id,或者根据 Animals 属性生成 id

创建静态 id 将如下所示:

`Animals(id: "animal-fred", name: "fred", animalTypes: [.shiba, .lab])`

根据属性生成 id 可能如下所示:

Animals: Identifiable {
    var id: {
        // We concatenate the name with each of the `AnimalsTypes`
        // by mapping the `AnimalsTypes` to their relevant strings
        // and joining them by a comma.
        // The resulting `id` might look like:
        // "fred-shiba,lab"
        return name + "-" + animalTypes.map { [=13=].rawValue }.joined(separator: ",")
    }
    var name: String
    var animalTypes: [AnimalTypes]
}

基于 Animals 属性生成 id 的方法效果很好,但如果您创建两个具有完全相同属性的 Animals,您的逻辑将无法区分它们,因为它们会生成相同的 id。然后他们将在 UserDefaults 中获取并设置相同的值,因为他们将共享相同的密钥。

如果您打算让应用的用户动态创建这些动物,则需要将它们存储在 CoreDataUserDefaults 中。但是,如果您的动物将全部由您创建,您可以静态定义 ID 或根据 Animals 属性生成它们。