如何使用 ForEach 中制作的 TextField 更新结构的属性

Posted

技术标签:

【中文标题】如何使用 ForEach 中制作的 TextField 更新结构的属性【英文标题】:How to update attributes of a struct with TextFields made in ForEach 【发布时间】:2021-09-11 00:47:44 【问题描述】:

在 SwiftUI 中,我有一个菜单项列表,每个项都有名称、价格等。有一堆类别,每个类别下都有一个项目列表。

struct ItemList: Identifiable, Codable 
    var id: Int
    var name: String
    var picture: String
    var list: [Item]
    
    @State var newItemName: String

我一直在寻找一种方法来在每个类别中创建一个 TextField,以添加到其项目数组中。

通过 ForEach 循环创建 TextField 非常简单,但是我在尝试使用输入的文本将新项目添加到正确的类别时遇到了困难。

ForEach(menu.indices)  i in
    Section(header: Text(menu[i].name)) 
        ForEach(menu[i].list)  item in
            Text(item.name)
        
        TextField("New Type:", text: /*some kind of bindable here?*/) 
            menu[i].list.append(Item(name: /*the text entered above*/))
        
    

我考虑过使用 @Published 和 Observable Object,如 this other question,但我需要 ItemList 是一个 Codable 结构,所以我无法弄清楚如何将答案放在我的案例中。

TextField("New Type:", text: menu[i].$newItemName)

任何想法都将不胜感激,谢谢!

【问题讨论】:

【参考方案1】:

你只需要关注你的View

import SwiftUI

struct ExpandingMenuView: View 
    @State var menu: [ItemList] = [
        ItemList(name: "Milk Tea", picture: "", list: [ItemModel(name: "Classic Milk Tea"), ItemModel(name: "Taro milk tea")]),
        ItemList(name: "Tea", picture: "", list: [ItemModel(name: "Black Tea"), ItemModel(name: "Green tea")]),
        ItemList(name: "Coffee", picture: "", list: [])
        
    ]
    var body: some View 
        List
            //This particular setup is for ios15+
            ForEach($menu)  $itemList in
                ItemListView(itemList: $itemList)
            
        
    


struct ItemListView: View 
    @Binding var itemList: ItemList
    @State var newItemName: String = ""
    var body: some View 
        Section(header: Text(itemList.name)) 
            ForEach(itemList.list)  item in
                Text(item.name)
            
            TextField("New Type:", text: $newItemName, onCommit: 
                //When the user commits add to array and clear the new item variable
                itemList.list.append(ItemModel(name: newItemName))
                newItemName = ""
            )
        
    

struct ItemList: Identifiable, Codable 
    var id: UUID = UUID()
    var name: String
    var picture: String
    var list: [ItemModel]
    //@State is ONLY for SwiftUI Views
    //@State var newItemName: String

struct ItemModel: Identifiable, Codable 
    var id: UUID = UUID()
    var name: String
    

struct ExpandingMenuView_Previews: PreviewProvider 
    static var previews: some View 
        ExpandingMenuView()
    

如果您不使用 Xcode 13 和 iOS 15+,SO 中有许多解决方案可用于绑定数组元素。以下只是其中之一

ForEach(menu)  itemList in
    let proxy = Binding(get: itemList, set:  new in
        let idx = menu.firstIndex(where: 
            $0.id == itemList.id
        )!
        menu[idx] = new
    )
    ItemListView(itemList: proxy)

另请注意,使用indices 被认为是不安全的。您可以观看 WWDC2021 的 Demystifying SwiftUI 了解更多详情。

【讨论】:

ForEach($menu) $itemList in 语法不仅适用于 iOS 15,它还是 Swift 5.5 的功能。例如,如果在 Binding 上使用 enumerated(),则只需要 iOS 15。 是的,效果很好,谢谢!升级到 swift 5.5 有点困难,但是 Binding() 方法效果很好,真的很感激【参考方案2】:

您可以将ObservableObject 作为您的数据模型,存储类别,然后存储项目。

然后您可以使用 Swift 5.5 语法绑定到这些项目。这意味着我们可以写List($menu.categories) $category in /* ... */ 。然后,当我们写$category.newItem 时,我们在Category 中的newItem 属性上有一个Binding<String>

例子:

struct ContentView: View 
    @StateObject private var menu = Menu(categories: [
        Category(name: "Milk Tea", items: [
            Item(name: "Classic Milk Tea"),
            Item(name: "Taro Milk Tea")
        ]),
        Category(name: "Tea", items: [
            Item(name: "Black Tea"),
            Item(name: "Green Tea")
        ]),
        Category(name: "Coffee", items: [
            Item(name: "Black Coffee")
        ])
    ])

    var body: some View 
        List($menu.categories)  $category in
            Section(header: Text(category.name)) 
                ForEach(category.items)  item in
                    Text(item.name)
                

                TextField("New item", text: $category.newItem, onCommit: 
                    guard !category.newItem.isEmpty else  return 
                    category.items.append(Item(name: category.newItem))
                    category.newItem = ""
                )
            
        
    

class Menu: ObservableObject 
    @Published var categories: [Category]

    init(categories: [Category]) 
        self.categories = categories
    


struct Category: Identifiable 
    let id = UUID()
    let name: String
    var items: [Item]
    var newItem = ""


struct Item: Identifiable 
    let id = UUID()
    let name: String

结果:

【讨论】:

您可能希望在有人感到困惑之前添加合并导入行。 @ElTomato 但是它使用的是 SwiftUI,所以 Combine 已经被 SwiftUI 导入了 @ElTomato 为什么不呢?自己试一试。编译得很好。不过,请确保您使用的是 Swift 5.5。在这里导入 Combine 是多余的。 好的。你是对的关于结合。对此我很抱歉。 @ElTomato nw!如果你在 import SwiftUIcommand + click 然后查看定义,则从 SwiftUI 的第二个导入是 Combine。

以上是关于如何使用 ForEach 中制作的 TextField 更新结构的属性的主要内容,如果未能解决你的问题,请参考以下文章

使用列表时如何在 foreach 中使用 CheckBoxFor? [复制]

使用 ForEach 循环制作多行 Component() ? [SwiftUI]

如何将foreach中的元素添加到数组中

QTextEdit foreach

Knockout.js - 如何使用“foreach”以两种不同的方式显示项目列表?

如何在 Javascript 中使简单的 php 的 foreach 等效? [复制]