SwiftUI: сбой при удалении последней ссылки NavigationLink

В SwiftUI с macOS и iPadOS возникает сбой, когда я делаю количество NavigationLink элементов переменной, а затем пытаюсь удалить последний элемент. Можно удалить любой другой элемент, кроме последнего. При сбое я получаю распечатку журнала:

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

Кажется, что destination из NavigationLink сохраняется дольше, чем сам NavigationLink, что на короткий момент дает destination недопустимую привязку. Этот сбой проявляется только в том случае, если destination требуется Binding (например, чтобы иметь возможность редактировать некоторый контент). Если я использую Text Просмотреть как destination вместо TextField, сбой не произойдет.

Вот минимальный пример для воспроизведения:

import SwiftUI

struct ContentView: View {
    @State var strings = ["Hello World 1", "Hello World 2", "Hello World 3"]
    @State var selectedStringIndex: Int?
    
    var body: some View {
        NavigationView {
            VStack {
                Button("Remove Selected") { // when removing the last element => crash
                    if let selectedStringIndex = selectedStringIndex {
                        strings.remove(at: selectedStringIndex)
                    }
                }
                
                List(strings.indices, id: \.self, selection: $selectedStringIndex) { stringIndex in
                    NavigationLink(destination: TextField("Name", text: $strings[stringIndex]),
                                   tag: stringIndex, selection: $selectedStringIndex) {
                        Text(strings[stringIndex])
                    }
                }
            }
        }
    }
}

Какой рекомендуемый способ обработки динамического количества NavigationLink элементов?

Я использую Xcode 12.2 beta 4 на macOS 11.0.1 Beta


person John Henryk    schedule 12.11.2020    source источник
comment
Вы хотите найти ответ на эту проблему с помощью этого кода или хотите удалить NavigationLink?   -  person user    schedule 12.11.2020
comment
В идеале оба. В чем проблема с кодом?   -  person John Henryk    schedule 12.11.2020
comment
Итак, вы верите, что ваш код в порядке?   -  person user    schedule 12.11.2020
comment
Вы должны добавить несколько print операторов в свой button в if else разделах, чтобы понять, что происходит. Также посмотрите пример кода, который предоставляется в ContentView при создании проекта в Xcode 12.   -  person lorem ipsum    schedule 12.11.2020
comment
да, каждый раз, когда я нажимаю, Button strings.remove вызывается с правильным индексом. Сбой происходит внутри SwiftUI   -  person John Henryk    schedule 12.11.2020


Ответы (1)


Возможное решение - использовать пользовательский Binding и проверить, находится ли index в диапазоне:

func binding(for index: Int) -> Binding<String> {
    .init(get: {
        guard strings.indices.contains(index) else { return "" } // check if `index` is valid
        return strings[index]
    }, set: {
        strings[index] = $0
    })
}

Затем вы можете использовать эту привязку в NavigationLink:

NavigationLink(destination: TextField("Name", text: binding(for: stringIndex)), ...)

Также убедитесь, что вы сбросили selectedStringIndex после удаления:

Button("Remove Selected") {
    if let selectedStringIndex = selectedStringIndex {
        strings.remove(at: selectedStringIndex)
        self.selectedStringIndex = nil // reset here
    }
}
person pawello2222    schedule 12.11.2020