🦁
[SwiftUI]List + TextFieldで文字を編集可能なリストを実装
List
の中でTextField
を使ってデータを更新しようとした時、つまずいたのでまとめました。
参考にしていた記事
こちらの実装方法では、List
に使用されたデータがString
配列であったため、実際にプロダクトで使うstruct
などの形式にはなっていないのでそのままでは使えませんでした。
実装方法
Model
struct SampleModel {
var text: String
}
text
のみを定義します。let
ではなくvar
にします。
Listの中のRow
struct SampleRow: View {
@Binding var model: SampleModel
var onCommit: () -> Void
var body: some View {
TextField.init("tap to edit text", text: self.$model.text, onEditingChanged: { _ in
//nop
}, onCommit: {
self.onCommit()
})
}
}
変更を伝播させたいので、SampleModel
を@Binding
しています。このViewを使うときに@Binding
に適用できる形式にする必要があります。
ContentView
実際にList
を使う部分です。
struct ContentView: View {
@State var data: [SampleModel] = [
SampleModel(text: "abc"),
SampleModel(text: "def"),
SampleModel(text: "")
]
var body: some View {
NavigationView {
List {
//ForEach(self.data.indices) { index in
ForEach(self.data.indices, id: \.self) { index in /*2021/07/14 修正*/
SampleRow.init(model: self.$data[index]) {
debugPrint("onCommit")
debugPrint(self.data)
}
}.onDelete(perform: { indexSet in
self.data.remove(atOffsets: indexSet)
}).onMove(perform: { indices, newOffset in
self.data.move(fromOffsets: indices, toOffset: newOffset)
})
}.navigationBarTitle(Text("editable list"), displayMode: .inline)
.navigationBarItems(trailing: EditButton())
}
}
}
よくあるForEach(data, \.self)
などを使うと、SampleRow
にBinding
として値を渡すことができません。
なので、下記を使用します。
ForEach.init(_ data: Range<Int>, content: @escaping (Int) -> Content)
ForEach(self.data.indices) { index in
SampleRow.init(model: self.$data[index]) {
debugPrint("onCommit")
debugPrint(self.data)
}
}
data.indices
で取り出したindexを使い、SampleRow
にBinding
を渡しています。
変更が終わった後(returnした後)に値を書き出してみると、変更された後の値になっています。
サンプル
Discussion
<修正>
ForEach(self.data.indices) { index in
↓
ForEach(self.data.indices, id: .self) { index in
以下のエラーが出ていたため。