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])
为清楚起见编辑:
这里,fred
和fred2
大部分相同,但是id
属性对于这些实例中的每一个Animals
个会有所不同。因此,如果您尝试使用另一个的 id
访问一个的选择值,您将不会收到正确的值。您必须保留确切的 Animals
实例才能访问存储在 UserDefaults
中的值,或者至少根据需要保留其 id
。此规则适用于所有应用程序会话。如果您实例化一个 Animals
对象然后退出并重新启动应用程序,当同一个 Animals
对象是 re-instantiated 时,它将根据您的定义分配一个不同的 id
值Animals.id = UUID().uuidString
.
为了在应用程序会话中保留它们的 id
,您可以将动物存储在 CoreData
、UserDefaults
中,授予它们静态 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
中获取并设置相同的值,因为他们将共享相同的密钥。
如果您打算让应用的用户动态创建这些动物,则需要将它们存储在 CoreData
或 UserDefaults
中。但是,如果您的动物将全部由您创建,您可以静态定义 ID 或根据 Animals
属性生成它们。
我正在尝试使用 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])
为清楚起见编辑:
这里,fred
和fred2
大部分相同,但是id
属性对于这些实例中的每一个Animals
个会有所不同。因此,如果您尝试使用另一个的 id
访问一个的选择值,您将不会收到正确的值。您必须保留确切的 Animals
实例才能访问存储在 UserDefaults
中的值,或者至少根据需要保留其 id
。此规则适用于所有应用程序会话。如果您实例化一个 Animals
对象然后退出并重新启动应用程序,当同一个 Animals
对象是 re-instantiated 时,它将根据您的定义分配一个不同的 id
值Animals.id = UUID().uuidString
.
为了在应用程序会话中保留它们的 id
,您可以将动物存储在 CoreData
、UserDefaults
中,授予它们静态 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
中获取并设置相同的值,因为他们将共享相同的密钥。
如果您打算让应用的用户动态创建这些动物,则需要将它们存储在 CoreData
或 UserDefaults
中。但是,如果您的动物将全部由您创建,您可以静态定义 ID 或根据 Animals
属性生成它们。