Ошибка индекса SwiftUI ForEach вне допустимого диапазона при удалении строки

У меня есть ForEach блок и Stepper встроен в ListПосмотреть. СодержаниеList Первый раздел представления выглядит следующим образом:

ForEach(record.nodes.indices, id: \.self) { index in
    HStack {
        TextField("X", text: self.$record.nodes[index].xString)
        Spacer()
        Divider()
        TextField("Y", text: self.$record.nodes[index].yString)
        Spacer()
    }
}
Stepper("± node", onIncrement: {
    self.record.nodes.append(Node(x: 0, y: 0))
}, onDecrement: {
    self.record.nodes.removeLast()
})

Проблема, с которой я столкнулся, заключается в том, что при звонке self.record.nodes.removeLast(), приложение вылетает с Index out of rangeошибка. Я пытался решить это часами, но безуспешно.

Я изначально использовал onDelete, однако это вызвало ту же проблему.

Проект можно найти по адресу https://github.com/jacobcxdev/Timekeeper, эта ошибка возникает в RecordDetailView.swift.

3 ответа

описание проблемы

Ошибка индекса вне допустимого диапазона возникает из-за того, что init(_ data: Range<Int>, content: @escaping (Int) -> Content) инициализатор ForEachне позволяет нам динамически изменять массив. Для этого нам нужно использоватьinit(_ data: Data, content: @escaping (Data.Element) -> Content)инициализатор как @Asperi объяснил в комментарии. Однако он не работает и в этой ситуации, когда привязки вложены, как пояснил @JacobCXDev в ответе.

Обходной путь (я нашел лучшее решение, см. Ниже)

Используйте пользовательские привязки и дополнительное состояние. Пользовательские привязки решают проблемуForEachнад вложенными привязками. Кроме того, вам необходимо изменять состояние после каждого прикосновения к настраиваемой привязке, чтобы повторно отображать экран. Ниже приводится упрощенный (непроверенный) образец кода.

@State private var xString: String

ForEach(record.nodes) { node in
    let xStringBinding = Binding(
        get: { node.xString },
        set: {
            node.xString = $0
            self.xString = $0
        }
    )
    TextField("X", text: xStringBinding)
}

Решение (добавлено 10 июля 2020 г.)

Просто определите структуру представления для детей, как показано ниже.

ForEach(record.nodes) { node in
    NodeView(node: node)
}

struct NodeView: View {
    @ObservedObject var node: Node

    var body: some View {
        TextField("X", text: self.$node.xString)
    }
}

Он работает как следующие коды.

 struct Node {
var xString: String = "x"
var yString: String = "y"
var x: Int
var y: Int
 }

 struct RecordsNode {
var nodes : [Node]
 }

struct ContentView: View {



@State var record: RecordsNode = RecordsNode(nodes: [Node(x: 11, y: 11)])

var body: some View {

    Group{
    ForEach(record.nodes.indices, id: \.self) { index in
        HStack {
            TextField("X", text: self.$record.nodes[index].xString)
            Spacer()
            Divider()
            TextField("Y", text: self.$record.nodes[index].yString)
            Spacer()
        }
    }
    Stepper("± node", onIncrement: {
        self.record.nodes.append(Node(x: 0, y: 0))
    }, onDecrement: {
        self.record.nodes.removeLast()
    })}

}}

У меня была та же проблема, которую я решил, добавив условие if перед ForEach.

if (!IsDeleting) {
  ...
  .OnDecrement{
     IsDeleting := true
   }
}

какое-нибудь лучшее решение?

Другие вопросы по тегам