在 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 中使用索引。在这种情况下,最简单的做法是创建一个符合 Identifiablestruct 作为您的数据模型,而不是使用纯字符串。这大大简化了您的代码:

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 的包装器变量。核心数据实体符合 IdentifiableObservableObject,因此您将 $ 放在 ForEach 中。在 ForEach.

中使用任何 ObservableObject 都是如此

您应该避免在 ForEach 中像瘟疫一样使用 id: \.self。因为这些是不可识别的,所以 OS 在进行移动、删除或者如果你有两个相同的值和崩溃时会感到困惑。