【问题标题】:SwiftUI Textfields inside Lists列表中的 SwiftUI 文本字段
【发布时间】:2020-06-01 13:18:50
【问题描述】:

我想要一个包含行的列表,每行里面有 2 个文本字段。这些行应该保存在一个数组中,以便我可以将其他视图中的数据用于进一步的功能。如果 Textfield 中的文本发生更改,则应将文本保存在数组的正确条目中。 您还可以通过一个按钮将新行添加到列表中,这也应该更改行的数组。

目标是拥有一个键值对列表,每个键值对都可编辑,并且这些条目与当前文本一起保存。

有人可以帮助我和/或给我提示以解决此问题吗?

到目前为止,我已经尝试过这样的事情:

// the array of list entries     
@State var list: [KeyValue] = [KeyValue()]
// the List inside of a VStack  
List(list) { entry in
    KeyValueRow(list.$key, list.$value)
}
// the single item
struct KeyValue: Identifiable {
    var id = UUID()

    @State var key = ""
    @State var value = ""
}
// one row in the list with view elements
struct KeyValueRow: View {

    var keyBinding: Binding<String>
    var valueBinding: Binding<String>

    init(_ keyBinding: Binding<String>, _ valueBinding: Binding<String>){
        self.keyBinding = keyBinding
        self.valueBinding = valueBinding
    }

    var body: some View {
        HStack() {
            TextField("key", text: keyBinding)
            Spacer()
            TextField("value", text: valueBinding)
            Spacer()
        }
    }
}

另外,关于添加新条目的按钮。 问题是,如果我执行以下操作,视图中的列表将变为空白,并且所有内容再次为空 (可能相关:SwiftUI TextField inside ListView goes blank after filtering list items?)

Button("Add", action: {
    self.list.append(KeyValue())
})

【问题讨论】:

    标签: ios swiftui


    【解决方案1】:

    我不确定最佳做法是使视图与这样的数组中的状态保持同步,但这是使其工作的一种方法。

    对于模型,我添加了一个符合 Observable 对象的列表类,每个 KeyValue 项都会在更改时提醒它:

    class KeyValueList: ObservableObject {
    
        @Published var items = [KeyValue]()
    
        func update() {
            self.objectWillChange.send()
        }
    
        func addItem() {
            self.items.append(KeyValue(parent: self))
        }
    }
    
    class KeyValue: Identifiable {
    
        init(parent: KeyValueList) {
            self.parent = parent
        }
    
        let id = UUID()
        private let parent: KeyValueList
    
        var key = "" {
            didSet { self.parent.update() }
        }
        var value = "" {
            didSet { self.parent.update() }
        }
    }
    

    然后我可以简单地在行视图中只保留一个状态:

    struct KeyValueRow: View {
    
        @State var item: KeyValue
    
        var body: some View {
            HStack() {
                TextField("key", text: $item.key)
                Spacer()
                TextField("value", text: $item.value)
                Spacer()
            }
        }
    }
    

    对于列表视图:

    struct TextFieldList: View {
    
        @ObservedObject var list = KeyValueList()
    
        var body: some View {
    
            VStack {
                List(list.items) { item in
                    HStack {
                        KeyValueRow(item: item)
                        Text(item.key)
                    }
                }
                Button("Add", action: {
                    self.list.addItem()
                })
    
            }
        }
    }
    

    我只是在其中添加了一个额外的Text 进行测试,以查看它是否实时更新。

    我没有遇到您所描述的空白视图的添加按钮。这是否也为您解决了这个问题?

    【讨论】:

    • 哇,感谢您的帮助。我不知道如何让您添加的 Text(item.key) 元素显示在 app/emulation(just for testing) 中。在 TextField 中键入后,项目内部似乎没有任何变化(我将print(self.list.items) 打印到控制台,只设置了父级,其余的只是空字符串“”)而且我对视图没有任何问题在我再次点击按钮后空白。
    • 嗯,这段代码对我有用。您看到我将 KeyValue 从结构更改为类了吗?
    • 非常感谢您的工作。它现在正在工作。我觉得我太愚蠢了,我错过了这个变化。
    • 我很高兴听到它对您有用!在 SwiftUI 中使用数组作为状态是很棘手的,而且对我来说经常需要一些摆弄。
    【解决方案2】:

    无需搞砸课程,Observable,Identifiable。你可以用结构来做这一切。

    请注意,下面的版本可以很好地插入,但如果您尝试删除数组元素,则会失败:

    import SwiftUI
    
    // the single item
    struct KeyValue {
        var key: String
        var value: String
    }
    
    struct ContentView: View {
        @State var boolArr: [KeyValue] = [KeyValue(key: "key1", value: "Value1"), KeyValue(key: "key2", value: "Value2"), KeyValue(key: "key3", value: "Value3"), KeyValue(key: "key4", value: "Value4")]
    
        var body: some View {
            NavigationView {
                // id: \.self is obligatory if you need to insert
                List(boolArr.indices, id: \.self) { idx in
                    HStack() {
                        TextField("key", text: self.$boolArr[idx].key)
                        Spacer()
                        TextField("value", text: self.$boolArr[idx].value)
                        Spacer()
                    }
                }
                .navigationBarItems(leading:
                    Button(action: {
                        self.boolArr.append(KeyValue(key: "key\(UInt.random(in: 0...100))", value: "value\(UInt.random(in: 0...100))"))
                        print(self.boolArr)
                    })
                    { Text("Add") }
                    , trailing:
                    Button(action: {
                        self.boolArr.removeLast() // causes "Index out of range" error
                        print(self.boolArr)
                    })
                    { Text("Remove") })
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    更新: 让它也适用于删除的小技巧。

    import SwiftUI
    
    // the single item
    struct KeyValue {
        var key: String
        var value: String
    }
    
    struct KeyValueView: View {
        @Binding var model: KeyValue
        var body: some View {
            HStack() {
                TextField("Key", text: $model.key)
                Spacer()
                TextField("Value", text: $model.value)
                Spacer()
            }
        }
    }
    
    struct ContentView: View {
        @State var kvArr: [KeyValue] = [KeyValue(key: "key1", value: "Value1"), KeyValue(key: "key2", value: "Value2"), KeyValue(key: "key3", value: "Value3"), KeyValue(key: "key4", value: "Value4")]
    
        var body: some View {
            NavigationView {
                List(kvArr.indices, id: \.self) { i in
                    KeyValueView(model: Binding(
                      get: {
                        return self.kvArr[i]
                    },
                    set: { (newValue) in
                        self.kvArr[i] = newValue
                    }))
                }
                .navigationBarItems(leading:
                    Button(action: {
                        self.kvArr.append(KeyValue(key: "key\(UInt.random(in: 0...100))", value: "value\(UInt.random(in: 0...100))"))
                        print(self.kvArr)
                    })
                    { Text("Add") }
                    , trailing:
                    Button(action: {
                        self.kvArr.removeLast() // Works like a charm
                        print(self.kvArr)
                    })
                    { Text("Remove") })
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    【讨论】:

      【解决方案3】:

      iOS 15 的工作代码示例

      在 SwiftUI 中,Apple 建议将绑定直接传递到 List 构造函数中,并在 ViewBuilder 块中使用 @Binding 来遍历每个元素。

      Apple 建议使用这种方法而不是使用 Indices 来迭代集合,因为这不会在每次 TextField 值更改时重新加载整个列表(效率更高)。

      新语法也可以反向部署到以前版本的 SwiftUI 应用程序中。

      struct ContentView: View {
        @State var directions: [Direction] = [
          Direction(symbol: "car", color: .mint, text: "Drive to SFO"),
          Direction(symbol: "airplane", color: .blue, text: "Fly to SJC"),
          Direction(symbol: "tram", color: .purple, text: "Ride to Cupertino"),
          Direction(symbol: "bicycle", color: .orange, text: "Bike to Apple Park"),
          Direction(symbol: "figure.walk", color: .green, text: "Walk to pond"),
          Direction(symbol: "lifepreserver", color: .blue, text: "Swim to the center"),
          Direction(symbol: "drop", color: .indigo, text: "Dive to secret airlock"),
          Direction(symbol: "tram.tunnel.fill", color: .brown, text: "Ride through underground tunnels"),
          Direction(symbol: "key", color: .red, text: "Enter door code:"),
        ]
      
        var body: some View {
          NavigationView {
            List($directions) { $direction in
              Label {
                TextField("Instructions", text: $direction.text)
              }
            }
            .listStyle(.sidebar)
            .navigationTitle("Secret Hideout")
          }
        }
      }
      
      struct Direction: Identifiable {
        var id = UUID()
        var symbol: String
        var color: Color
        var text: String
      }
      

      【讨论】:

        【解决方案4】:

        斯威夫特 5.5

        这个版本的 swift 通过直接从数组中传递可绑定项来启用一行代码。

        struct DirectionsList: View {
            @Binding var directions: [Direction]
        
            var body: some View {
                List($directions) { $direction in
                    Label {
                        TextField("Instructions", text: $direction.text)
                    } icon: {
                        DirectionsIcon(direction)
                    }
                }
            }
        }
        

        【讨论】:

        • 不适合添加包含代码的图片!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-23
        • 2021-11-14
        • 2021-10-09
        • 2021-01-06
        • 2021-02-18
        相关资源
        最近更新 更多