【发布时间】:2020-12-19 23:09:28
【问题描述】:
经过数小时的调试,我发现错误出现在文件夹 ContentViews 中 MenuItemView 的 foreach 循环内。
应用程序崩溃,错误是:
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444.
信息:
我有一个ObservableObject 内部有一个Array 或Structs 作为数据存储。
问题:
ForEach 介于 0 和数组计数 + 1 之间。这样我就可以有一个额外的项目来添加新元素。在 ForEach 中检查索引是否在边界内(if (idx >= palettesOO.palettes.count) 然后显示加号)。
但是当我右键单击任何单元格并单击“删除”时它会崩溃。这将调用类Manager 中的函数RemovePalette。在那里,数据从ObservableObject 内的数组中删除 - 这也有效。
函数被调用后,应用程序崩溃(我知道这一点,因为我在函数调用后打印了一条消息)。我发现当视图被重绘(更新)时会发生崩溃。
如果我有一个不需要绑定的视图元素,例如 Text,那么它可以工作,如果它需要绑定,例如 TextField,它就会崩溃。 Text(palettesOO.palettes[idx].palName) 在 ForEach 内的 else 内有效,但需要绑定的视图元素或子视图不起作用:TextField("", text: $palettesOO.palettes[idx].palName) 崩溃。
我尝试使用 these 之类的内容修改 ForEach,但没有成功。
代码和数据:
class PalettesOO: ObservableObject {
@Published var palettes = [Palette]()
}
MenuItemView:
struct MenuItemView: View {
@ObservedObject var palettesOO = PalettesOO()
var body: some View {
VStack {
SectionView("Palettes") {
LazyVGrid(columns: Array(repeating: GridItem(.fixed(viewCellSize), spacing: viewCellSpacing), count: viewColCount), spacing: viewCellSpacing) {
ForEach(0..<palettesOO.palettes.count + 1, id: \.self) { idx in
if (idx >= palettesOO.palettes.count) {
Button(action: {
newPalettePopover = true
}, label: {
Image(systemName: "plus.square").font(.system(size: viewCellSize))
}).buttonStyle(PlainButtonStyle())
}
else {
// Works
Text(palettesOO.palettes[idx].palName)
// Does not work
TextField("ASD", text: $palettesOO.palettes[palettesOO.palettes.count - 1].palName).frame(width: 100, height: 100).background(Color.red).contextMenu(ContextMenu(menuItems: {
Button(action: {}, label: {
Text("Rename")
})
Button(action: { Manager.RemovePalette(name: palettesOO.palettes[idx].palName); print("Len \(palettesOO.palettes.count)") }, label: {
Text("Delete")
})
}))
// Original code, also crashes (PalettePreviewView is a custom subview which does not matter for this)
// PalettePreviewView(palette: $palettesOO.palettes[palettesOO.palettes.count - 1], colNum: $previewColCount, cellSize: $viewCellSize).cornerRadius(viewCellSize / 100 * viewCellRadius).contextMenu(ContextMenu(menuItems: {
// Button(action: {}, label: {
// Text("Rename")
// })
// Button(action: { Manager.RemovePalette(name: palettesOO.palettes[idx].palName); print("Len \(palettesOO.palettes.count)") }, label: {
// Text("Delete")
// })
// }))
}
}
}
}
}.padding().fixedSize()
}
}
经理:
class Manager {
static func RemovePalette(name: String) {
var url = assetFilesDirectory(name: "Palettes", shouldCreate: true)
url?.appendPathComponent("\(name).json")
if (url == nil) {
return
}
do {
try FileManager.default.removeItem(at: url!)
} catch let error as NSError {
print("Error: \(error.domain)")
}
LoadAllPalettes()
UserDefaults.standard.removeObject(forKey: "\(k_paletteIndicies).\(name)")
}
}
我知道这样复杂的问题不适合在 Stack Overflow 上发布,但我想不出其他办法。
项目版本控制在我的GitHub 上是公开的,以防需要找到解决方案。
编辑 2020 年 12 月 21 日 @ 晚上 8:30: 感谢@SHS,它现在就像一个魅力! 这是最终的工作代码:
struct MenuItemView: View {
@ObservedObject var palettesOO = PalettesOO()
var body: some View {
VStack {
...
ForEach(0..<palettesOO.palettes.count + 1, id: \.self) { idx in
...
//// @SHS Changed :-
Safe(self.$palettesOO.palettes, index: idx) { binding in
TextField("ASD", text: binding.palName).frame(width: 100, height: 100).background(Color.red).contextMenu(ContextMenu(menuItems: {
Button(action: {}, label: {
Text("Rename")
})
Button(action: { Manager.RemovePalette(name: binding.wrappedValue.palName); print("Len \(palettesOO.palettes.count)") }, label: {
Text("Delete")
})
}))
}
}
}
...
}
}
//// @SHS Added :-
//// You may keep the following structure in different file or Utility folder. You may rename it properly.
struct Safe<T: RandomAccessCollection & MutableCollection, C: View>: View {
typealias BoundElement = Binding<T.Element>
private let binding: BoundElement
private let content: (BoundElement) -> C
init(_ binding: Binding<T>, index: T.Index, @ViewBuilder content: @escaping (BoundElement) -> C) {
self.content = content
self.binding = .init(get: { binding.wrappedValue[index] },
set: { binding.wrappedValue[index] = $0 })
}
var body: some View {
content(binding)
}
}
【问题讨论】:
-
如果您不打算显示代码行,您在寻求什么样的帮助?如果您尝试在 ForEach 循环中删除一条记录,显然,无论如何,应用程序都会崩溃。
-
我不能在这里显示代码,因为这太多了,问题太复杂了,对不起。但代码在我的GitHub