动态添加带有每个循环的文本场和SwiftUi中的按钮

发布于 2025-01-27 04:48:22 字数 461 浏览 2 评论 0原文

我正在尝试使用foreach循环在表单部分中动态添加带有按钮的文本字段。

Form {
                ForEach(0..<numberOfItems, id: \.self) { _ in
                    TextField("", text: $listItemEntry)
                }
                Button("Add Item") {
                    numberOfItems = numberOfItems + 1
                }
            }

我将如何替换$ listitementry当前绑定到状态变量,以便我可以单独输入每个文本字段。作为奖励,将这些条目保存到Coredata的最佳方法是我真的不知道从哪里开始。

I am trying to use a ForEach loop to dynamically add textfields inside a form section with a button.

Form {
                ForEach(0..<numberOfItems, id: \.self) { _ in
                    TextField("", text: $listItemEntry)
                }
                Button("Add Item") {
                    numberOfItems = numberOfItems + 1
                }
            }

How would I replace the $listItemEntry which is currently bound to a state variable so that I can individually type in each text field. As a bonus, what would be the best way to save these entries to CoreData, as I don't really know where to start.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

柠檬心 2025-02-03 04:48:22

您正在尝试从演示代码中弄清楚这一点的问题。如果有一些解决方法,则应真正避免在lists中使用索引。在这种情况下,最简单的事情是创建一个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

假设属性是非选择性的,或者您创建了包装器始终返回非选项的变量。核心数据实体符合识别observableObject,因此您将$放在foreach中。使用 中的任何observableObject是正确的。

您应该避免在中使用ID:\ .self foreach喜欢瘟疫。因为这些是无法识别的,所以操作系统在执行移动,删除,或者您有两个原本相同的值并崩溃时会感到困惑。

The issue you are having boils down to trying to figure this out from demonstration code. You should really avoid using indices in Lists if at all possible, though there are some workarounds. In this case, the easiest thing to do is to create a struct conforming to Identifiable as your data model, instead of using a plain string. This greatly simplifies your code:

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
}

While it is a bit outside of the scope of the question, the ForEach for a Core Data entity would be defined as:

ForEach(listItemEntries) { listItemEntry in

ASSUMING the attribute was non-optional or you created a wrapper variable that always returned a non-optional. Core Data entities conform to Identifiable and ObservableObject so you drop the $ in the ForEach. This is true using any ObservableObject in a ForEach.

You should avoid using id: \.self in ForEach like the plague. Because these are not Identifiable, the OS can get confused when doing moves, deletes or if you have two otherwise identical values and crash.

一张白纸 2025-02-03 04:48:22

您可以遵循的方法是将值存储在视图模型中,该视图模型必须是类型observableObject。您可以使用字典始终根据其索引找到值。

要单独具有每个文本字段的@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] = ""
            }
        }
    }
}

The approach you can follow is to store the values in a view model, which has to be a class of type ObservableObject. You can use a dictionary to always find the values based on their index.

To individually have a @State variable for each text field, just use a separated view, with a dedicated variable. This subview will read the same view model, therefore also the same dictionary, and will contain the function to store the value.

Iterate over the keys of the dictionary in the ForEach of your main view, and call the subview that contains the text field.

Here is the code, for you to improve based on your program:

// 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] = ""
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文