Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
671 views
in Technique[技术] by (71.8m points)

swift - Deletable Table with TextField on SwiftUI

Environment

  • Xcode 11.2.1 (11B500)

Problem

In order to implement editable teble with TextField on SwiftUI, I used ForEach(0..<items.count) to handle index.

import SwiftUI

struct DummyView: View {
    @State var animals: [String] = ["??", "??"]

    var body: some View {
        List {
            EditButton()
            ForEach(0..<animals.count) { i in
                TextField("", text: self.$animals[i])
            }
        }
    }
}

However, problems arise if the table is changed to be deleteable.

import SwiftUI

struct DummyView: View {
    @State var animals: [String] = ["??", "??"]

    var body: some View {
        List {
            EditButton()
            ForEach(0..<animals.count) { i in
                TextField("", text: self.$animals[i]) // Thread 1: Fatal error: Index out of range
            }
            .onDelete { indexSet in
                self.animals.remove(atOffsets: indexSet) // Delete "??" from animals
            }
        }
    }
}

Thread 1: Fatal error: Index out of range when delete item

?? has been removed from animals and the ForEach loop seems to be running twice, even though animals.count is 1.

(lldb) po animals.count
1

(lldb) po animals
? 1 element
  - 0 : "??"

Please give me advice on the combination of Foreach and TextField.
Thanks.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Ok, the reason is in documentation for used ForEach constructor (as you see range is constant, so ForEach grabs initial range and holds it):

/// Creates an instance that computes views on demand over a *constant*
/// range.
///
/// This instance only reads the initial value of `data` and so it does not
/// need to identify views across updates.
///
/// To compute views on demand over a dynamic range use
/// `ForEach(_:id:content:)`.
public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)

So the solution would be to provide dynamic container. Below you can find a demo of possible approach.

Full module code

import SwiftUI

struct DummyView: View {
    @State var animals: [String] = ["??", "??"]

    var body: some View {
        VStack {
            HStack {
                EditButton()
                Button(action: { self.animals.append("Animal (self.animals.count + 1)") }, label: {Text("Add")})
            }
            List {
                ForEach(animals, id: .self) { item in
                    EditorView(container: self.$animals, index: self.animals.firstIndex(of: item)!, text: item)
                }
                .onDelete { indexSet in
                    self.animals.remove(atOffsets: indexSet) // Delete "??" from animals
                }
            }
        }
    }
}

struct EditorView : View {
    var container: Binding<[String]>
    var index: Int

    @State var text: String

    var body: some View {
        TextField("", text: self.$text, onCommit: {
            self.container.wrappedValue[self.index] = self.text
        })
    }
}

struct TestForEachCapture_Previews: PreviewProvider {
    static var previews: some View {
        DummyView()
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...