Как правильно использовать динамические привязки членов в SwiftUI?
Я пытаюсь использовать SwiftUI Binding
члены из @Binding
переменные (благодаря поддержке @dynamicMemberLookup
), но даже на простом примере я могу воссоздать несколько проблем. Я предполагаю, что я использую его неправильно, но документация и примеры в Интернете говорят об обратном.
Основная проблема (воспроизводимая на Catalina, Big Sur и iPadOS 13 и 14) заключается в том, что удаление элемента при открытом представлении вызывает сбой с ошибкой индекса вне допустимого диапазона.
Fatal error: Index out of range: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
Вторая проблема возникает в текстовом поле на Catalina, при попытке редактирования текста скрывается левый / навигационный вид. (В Big Sur редактирование текста скрывает правый / подробный вид, который, как я полагаю, является другим проявлением той же проблемы из-за улучшений в представлениях навигации.)
struct Child: Identifiable, Hashable {
var id = UUID()
var bar: String = "Text"
func hash(into hasher: inout Hasher) {
self.id.hash(into: &hasher)
}
}
struct ChildView: View {
let child: Child
var body: some View {
Text(child.bar)
}
}
struct ChildEditor: View {
@Binding var child: Child
var body: some View {
TextField("Text", text: self.$child.bar)
}
}
struct ContentView: View {
@State var children: [Child] = []
func binding(for child: Child) -> Binding<Child> {
guard let it = children.firstIndex(of: child) else {
fatalError()
}
return $children[it]
}
var plusButton: Button<Image> {
return Button(action: {
self.children.append(Child())
}) {
Image(systemName: "plus")
}
}
func ParentList<Content: View>(_ content: () -> Content) -> some View {
#if os(macOS)
return List(content: content)
.toolbar {
ToolbarItem {
self.plusButton
}
}
// uncomment for 10.15
// return List {
// self.plusButton
// content()
// }
#elseif os(iOS)
return List(content: content)
.navigationBarItems(trailing: self.plusButton)
#endif
}
var body: some View {
NavigationView {
ParentList {
ForEach(children) { child in
NavigationLink(destination: ChildEditor(child: self.binding(for: child))) {
ChildView(child: child)
}
}
.onDelete { offsets in
self.children.remove(atOffsets: offsets)
}
}
}
}
}
Мое базовое предположение заключалось бы в том, что Binding
по сути, хранит указатель, поэтому при удалении указатель станет недействительным и вызовет сбой, и что редактирование текстового поля вызывает обновление представления родительского представления, делая недействительным текущий контент (это поддерживается Big Sur, иногда жалующимся на то, что переменная состояния была изменена во время обновления представления, хотя она правильно используется только для инициализации TextField
). Однако изменение на использование типа класса и@ObservedObject
/@EnvironmentObject
(или @StateObject
) откладывает сбой (в Catalina и iPadOS 13/14) до того момента, когда любое другое действие навигации будет выполнено или не окажет никакого эффекта (в Big Sur). С использованиемtag
вариант в NavigationLink
отклонить представление, если оно было удалено, также не удалось.
Первый вопрос: что я делаю не так? Если ответ на этот вопрос - "Все", как следует управлять массивом данных в представлении верхнего уровня и создавать привязки к членам для вложенных подпредставлений?