SwiftUI索引超出ForEach范围 [英] SwiftUI Index out of range in ForEach
问题描述
经过数小时的调试,我发现错误是文件夹 ContentViews
中 MenuItemView
中的foreach循环内.
After hours of debugging I figured out the error is inside the foreach loop in MenuItemView
in the folder ContentViews
.
应用程序崩溃,错误是:
The app crashes and the error is:
致命错误:索引超出范围:文件Swift/ContiguousArrayBuffer.swift,第444行
.
信息:
我有一个 ObservableObject
,其中的 Array
为 Structs
,作为数据存储.
I have got an ObservableObject
with an Array
of Structs
inside as data storage.
问题:
ForEach在0到数组计数+ 1之间.这样我可以有一个额外的项来添加新元素.在ForEach中检查索引是否在范围内( if(idx> = palettesOO.palettes.count)
然后显示加号).
The ForEach goes between 0 and the array count + 1. This is so I can have an extra item for adding new elements. In the ForEach is a check if the index is inside the bounds (if (idx >= palettesOO.palettes.count)
then show the plus).
但是当我右键单击任何单元格并单击删除"时,它会崩溃.这将在类 Manager
中调用函数 RemovePalette
.在那里,数据从 ObservableObject
内的数组中删除-这也起作用.
But it crashes when I right click any cell and click "Remove". This calls the function RemovePalette
in the class Manager
. There the data gets removed from the array inside the ObservableObject
- this also works.
此函数被调用后,应用程序崩溃(我知道这是因为我在函数调用后打印了一条消息).我发现当视图重绘(更新)时会发生崩溃.
AFTER the function gets called the app crashes (I know this because I printed a message after the function call). I figured out that the crash occurs when the view gets redrawn (updated).
如果我有一个不需要绑定的view元素,例如 Text
,那么它就可以工作,如果它需要绑定,例如 TextField
崩溃.在ForEach内部else中的 Text(palettesOO.palettes [idx] .palName)
起作用,但是需要绑定的视图元素或子视图不起作用: TextField(",文本:$ palettesOO.palettes [idx] .palName)
崩溃.
If I have a view element which does not need a binding, for example a Text
, then it works, if it needs a binding, for example a TextField
it crashes. Text(palettesOO.palettes[idx].palName)
inside of the else inside the ForEach works but view elements or subviews which require Bindings do not work: TextField("", text: $palettesOO.palettes[idx].palName)
crashes.
我尝试使用之类的东西来修改ForEach.这些,但没有成功.
I have tried modifying the ForEach with things like these but with no success.
代码和数据:
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上发布,但我想不出其他任何方式.
I know that such complex problems are not good to post on Stack Overflow but I can't think of any other way.
项目版本控制在我的 GitHub 上公开,以防需要找到解决方案.
The project version control is public on my GitHub, in case it's needed to find a solution.
EDIT 2020年12月21日下午8:30:感谢@SHS,它现在就像一个魅力!这是最终的工作代码:
EDIT 12/21/2020 @ 8:30pm: Thanks to @SHS it now works like a charm! Here is the final working code:
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)
}
}
推荐答案
创建如下结构
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)
}
}
然后包装您的代码以按以下方式访问它
Then wrap your code for accessing it as under
Safe(self.$palettesOO.palettes, index: idx) { binding in
//Text(binding.wrappedValue.palName)
TextField("ASD", text: binding.palName)
//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: binding.wrappedValue.palName); print("Len \(palettesOO.palettes.count)") }, label: {
Text("Delete")
})
}))
}
我希望这可以为您提供帮助(直到在Swift中得到纠正)
I hope this can help you ( till it is corrected in Swift )
这篇关于SwiftUI索引超出ForEach范围的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!