在 swiftUI 中动态添加带有 for each 循环和按钮的文本字段
Dynamically add textfields with a for each loop and a button in swiftUI
我正在尝试使用 ForEach
循环在带有按钮的表单部分中动态添加文本字段。
Form {
ForEach(0..<numberOfItems, id: \.self) { _ in
TextField("", text: $listItemEntry)
}
Button("Add Item") {
numberOfItems = numberOfItems + 1
}
}
如何替换当前绑定到状态变量的 $listItemEntry
,以便我可以在每个文本字段中单独键入。作为奖励,将这些条目保存到 CoreData 的最佳方法是什么,因为我真的不知道从哪里开始。
您可以遵循的方法是将值存储在视图模型中,它必须是类型 ObservableObject
的 class。您可以使用字典始终根据索引查找值。
要为每个文本字段单独设置一个 @State
变量,只需使用带有专用变量的单独视图。此子视图将读取相同的视图模型,因此也是相同的字典,并将包含存储值的函数。
遍历主视图 ForEach
中字典的键,并调用包含文本字段的子视图。
代码如下,供大家根据自己的程序进行改进:
// Your view model
class ViewModel: ObservableObject {
// Variable of dictionary type
@Published var list = [Int: String]()
}
// Dedicated view for each text field
struct Field: View {
// Read the view model, to store the value of the text field
@EnvironmentObject var viewModel: ViewModel
// Index: where in the dictionary the value will be stored
let index: Int
// Dedicated state var for each field
@State private var field = ""
var body: some View {
VStack {
TextField("Enter text:", text: $field)
Button("Confirm", action: store)
}
}
// Store the value in the dictionary
private func store() {
viewModel.list[index] = field
}
}
struct Example: View {
// Create the view model instance
@StateObject var viewModel = ViewModel()
var body: some View {
Form {
// Read the keys of the dictionary
ForEach(viewModel.list.keys.sorted(), id: \.self) { index in
VStack {
Text(viewModel.list[index] ?? "")
.foregroundColor(.green)
Field(index: index)
// Pass the view model instance to the Field
.environmentObject(viewModel)
}
}
Button("Add one item") {
// Add the next key with an empty string
let currentCount = viewModel.list.count
viewModel.list[currentCount + 1] = ""
}
}
}
}
您遇到的问题归结为试图从演示代码中解决这个问题。尽管有一些解决方法,但您应该尽可能避免在 Lists
中使用索引。在这种情况下,最简单的做法是创建一个符合 Identifiable
的 struct
作为您的数据模型,而不是使用纯字符串。这大大简化了您的代码:
struct TextFieldDynamicAdd: View {
// I made this a @State var, but it could just as easily be a view model
@State var listItemEntries: [ListItemEntry] = []
var body: some View {
Form {
// Since you need to use a binding inside the ForEach, you need to use $listItemEntries
// and $listItemEntry in your declaration. Since ListItemEntry is Identifiable, no need for
// a id: \.self.
ForEach($listItemEntries) { $listItemEntry in
TextField("", text: $listItemEntry.text)
}
Button("Add Item") {
listItemEntries.append(ListItemEntry(text: "Item \(listItemEntries.count + 1)"))
}
}
}
}
struct ListItemEntry: Identifiable {
let id = UUID()
var text: String
}
虽然有点超出问题的范围,但核心数据实体的 ForEach
将定义为:
ForEach(listItemEntries) { listItemEntry in
假设该属性是 non-optional,或者您创建了一个始终返回 non-optional 的包装器变量。核心数据实体符合 Identifiable
和 ObservableObject
,因此您将 $
放在 ForEach
中。在 ForEach
.
中使用任何 ObservableObject
都是如此
您应该避免在 ForEach
中像瘟疫一样使用 id: \.self
。因为这些是不可识别的,所以 OS 在进行移动、删除或者如果你有两个相同的值和崩溃时会感到困惑。
我正在尝试使用 ForEach
循环在带有按钮的表单部分中动态添加文本字段。
Form {
ForEach(0..<numberOfItems, id: \.self) { _ in
TextField("", text: $listItemEntry)
}
Button("Add Item") {
numberOfItems = numberOfItems + 1
}
}
如何替换当前绑定到状态变量的 $listItemEntry
,以便我可以在每个文本字段中单独键入。作为奖励,将这些条目保存到 CoreData 的最佳方法是什么,因为我真的不知道从哪里开始。
您可以遵循的方法是将值存储在视图模型中,它必须是类型 ObservableObject
的 class。您可以使用字典始终根据索引查找值。
要为每个文本字段单独设置一个 @State
变量,只需使用带有专用变量的单独视图。此子视图将读取相同的视图模型,因此也是相同的字典,并将包含存储值的函数。
遍历主视图 ForEach
中字典的键,并调用包含文本字段的子视图。
代码如下,供大家根据自己的程序进行改进:
// Your view model
class ViewModel: ObservableObject {
// Variable of dictionary type
@Published var list = [Int: String]()
}
// Dedicated view for each text field
struct Field: View {
// Read the view model, to store the value of the text field
@EnvironmentObject var viewModel: ViewModel
// Index: where in the dictionary the value will be stored
let index: Int
// Dedicated state var for each field
@State private var field = ""
var body: some View {
VStack {
TextField("Enter text:", text: $field)
Button("Confirm", action: store)
}
}
// Store the value in the dictionary
private func store() {
viewModel.list[index] = field
}
}
struct Example: View {
// Create the view model instance
@StateObject var viewModel = ViewModel()
var body: some View {
Form {
// Read the keys of the dictionary
ForEach(viewModel.list.keys.sorted(), id: \.self) { index in
VStack {
Text(viewModel.list[index] ?? "")
.foregroundColor(.green)
Field(index: index)
// Pass the view model instance to the Field
.environmentObject(viewModel)
}
}
Button("Add one item") {
// Add the next key with an empty string
let currentCount = viewModel.list.count
viewModel.list[currentCount + 1] = ""
}
}
}
}
您遇到的问题归结为试图从演示代码中解决这个问题。尽管有一些解决方法,但您应该尽可能避免在 Lists
中使用索引。在这种情况下,最简单的做法是创建一个符合 Identifiable
的 struct
作为您的数据模型,而不是使用纯字符串。这大大简化了您的代码:
struct TextFieldDynamicAdd: View {
// I made this a @State var, but it could just as easily be a view model
@State var listItemEntries: [ListItemEntry] = []
var body: some View {
Form {
// Since you need to use a binding inside the ForEach, you need to use $listItemEntries
// and $listItemEntry in your declaration. Since ListItemEntry is Identifiable, no need for
// a id: \.self.
ForEach($listItemEntries) { $listItemEntry in
TextField("", text: $listItemEntry.text)
}
Button("Add Item") {
listItemEntries.append(ListItemEntry(text: "Item \(listItemEntries.count + 1)"))
}
}
}
}
struct ListItemEntry: Identifiable {
let id = UUID()
var text: String
}
虽然有点超出问题的范围,但核心数据实体的 ForEach
将定义为:
ForEach(listItemEntries) { listItemEntry in
假设该属性是 non-optional,或者您创建了一个始终返回 non-optional 的包装器变量。核心数据实体符合 Identifiable
和 ObservableObject
,因此您将 $
放在 ForEach
中。在 ForEach
.
ObservableObject
都是如此
您应该避免在 ForEach
中像瘟疫一样使用 id: \.self
。因为这些是不可识别的,所以 OS 在进行移动、删除或者如果你有两个相同的值和崩溃时会感到困惑。