Список SwiftUI (2.0) с дочерними элементами вылетает при попытке удалить последний элемент

У меня есть следующий код для списка SwiftUI с детьми, адаптированный из другого ответа StackOverflow:

SwiftUI 2.0 Список с детьми - как сделать так, чтобы область нажатия кнопки раскрытия информации покрывала весь элемент списка

struct ContentView: View {

    @EnvironmentObject var goodies: GlobalGoodies
    
    func listItem(for item: User) -> some View {
        Text(item.name)
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(Array(goodies.users.enumerated()), id: \.1.id) { i, group in
                    DisclosureGroup(isExpanded: $goodies.users[i].isExpanded) {
                        ForEach(group.children ?? []) { item in
                            listItem(for:item)
                        }
                        .opacity(goodies.users[i].opacity)
                    } label: {
                        listItem(for: group)
                        .contentShape(Rectangle())
                        .onTapGesture {
                            withAnimation {
                                goodies.users[i].opacity = goodies.users[i].isExpanded ? 0.0 : 1.0
                                goodies.users[i].isExpanded.toggle()
                            }
                        }
                    }
                }
                .onDelete(perform: delete)
            }
        }
    }

    func delete(at offsets: IndexSet) {
        goodies.users.remove(atOffsets: offsets)
    }
}

Исходный код для класса GlobalGoodies и класса User, адаптированный из Пример руководства по HackingWithSwift.

struct User: Identifiable {
    let id = UUID()
    var name: String
    var children: [User]? = nil
    
    var isExpanded = false
    var opacity = 0.0
}

class GlobalGoodies: ObservableObject {
    @Published var users = [
        User(name: "Paul", children: [
            User(name: "Jenny")
        ]),
        User(name: "Taylor", children: [
            User(name: "Tyler"),
            User(name: "Luna")
        ]),
        User(name: "Adele", children: nil)
    ]
}

Я могу нормально просматривать эти элементы и без проблем удалить два (родительских) элемента. Однако, когда я пытаюсь удалить последний оставшийся (родительский) элемент в списке, приложение вылетает с таким сообщением:

Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444

Я знаю, что другие сталкивались с аналогичными ошибками с тем же сообщением при использовании List, но эта ошибка возникает только при использовании этой конкретной настройки для отображения расширяемых дочерних элементов. Я следил за вышеупомянутым кодом учебника HackingWithSwift (который представляет собой обычный линейный список), и я могу удалить все элементы в нем без проблем.

Я пробовал использовать новый List(children:), но по какой-то причине не могу добавить к нему onDelete.

Любая помощь в определении местоположения этой ошибки была бы замечательной, поскольку я пробовал устанавливать точки останова и т.д., чтобы увидеть, где что-то пойдет не так. Элемент удаляется номинально, но сбой, похоже, происходит либо во время, либо после вызова body.

Заранее благодарю за любую помощь!


person themathsrobot    schedule 08.08.2020    source источник


Ответы (1)


Кажется, я смог понять это сам после небольшого исследования. Ошибка вне допустимого диапазона в этой строке:

DisclosureGroup(isExpanded: $goodies.users[i].isExpanded) {

(Я смог сказать, заменив на .constant(true), что остановило сбой.)

Мне удалось исправить это с помощью функции, которая дает настраиваемую привязку с учетом индекса i, которая проверяет, находится ли индекс в диапазоне, прежде чем обращаться к нему:

func binding(for index: Int) -> Binding<Bool> {
        Binding<Bool>(
            get: {
                if index > goodies.users.count - 1{
                    return false
                }
                return goodies.users[index].isExpanded
            },
            set: {
                if index > goodies.users.count - 1 {
                    // do nothing
                } else {
                    goodies.users[index].isExpanded = $0
                }
            }
        )
}

Использование затронутой линии:

DisclosureGroup(isExpanded: binding(for: i)) {

Это устраняет проблему сбоя. Раньше это было немного сложно определить, потому что я не понимал, что привязка была доступна после удаления элемента списка. Надеюсь, это поможет кому-то в таком же положении.

person themathsrobot    schedule 08.08.2020