[英]Deleting item in tableView by removing item from array crashes with fatal error: Index out of range
[英]SwiftUI - removing item from list crashes with Fatal error: Index out of range
我在 SwiftUI 中遇到 List 问题。 从列表中删除任何项目都会崩溃,错误为Fatal error: Index out of range
。 它不适用于onDelete
方法,也不适用于我的自定义函数。 我究竟做错了什么?
这是macOS应用程序,而不是 iOS。 我在 macOS 10.15.1 上使用 Xcode 11.2.1。
这是我的代码:
import SwiftUI
struct TodoItem: Identifiable {
var id = UUID()
var name: String
var isCompleted = false
}
struct TodoRow: View {
@Binding var todo: TodoItem
@State var buttonHover: Bool = false
var index: Int
var removeTodo: (_ index: Int) -> Void
func toggleTodo() {
self.$todo.isCompleted.wrappedValue.toggle()
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Button(action: toggleTodo) {
Image(nsImage: NSImage(named: NSImage.Name(NSImage.menuOnStateTemplateName))!)
.resizable()
.frame(width: 8, height: 8)
.padding(3)
.opacity(todo.isCompleted ? 1 : buttonHover ? 0.5 : 0)
}.buttonStyle(PlainButtonStyle())
.background(Capsule().stroke(Color.primary, lineWidth: 1))
.onHover(perform: { val in self.buttonHover = val })
Text("\(todo.name)").strikethrough(todo.isCompleted, color: Color.primary)
}.opacity(todo.isCompleted ? 0.35 : 1)
Divider().fixedSize(horizontal: false, vertical: true).frame(height: 1)
}.contextMenu {
Button(action: {
self.removeTodo(self.index)
}) {
Text("Remove")
}
}
}
}
struct TodoList: View {
var listName: String
@State var newTodo: String = ""
@State var todos: [TodoItem] = []
@State var showCompleted = false
func addTodo() {
let trimmedTodo = newTodo.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedTodo.isEmpty {
todos.insert(TodoItem(name: trimmedTodo), at: 0)
newTodo = ""
}
}
func removeTodo(index: Int) -> Void {
// remove is crashing the app :(
self.todos.remove(at: index)
}
var body: some View {
return VStack(alignment: .leading) {
Text("\(listName)").font(.system(size: 20))
HStack {
TextField("New todo...", text: $newTodo)
NativeButton("Add", keyEquivalent: .return) {
self.addTodo()
}
}
List {
ForEach(todos.indices.filter { self.showCompleted || !self.todos[$0].isCompleted }, id: \.self) { index in
TodoRow(todo: self.$todos[index], index: index, removeTodo: self.removeTodo)
}.onDelete{offsets in
// remove is crashing the app :(
self.todos.remove(atOffsets: offsets)
}
}
Toggle(isOn: $showCompleted) {
Text("Show completed")
}
}.padding().frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
}
struct TodoList_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
感谢您的帮助,并为混乱的代码感到抱歉,我还是 Swift 和 SwiftUI 的新手。
所以fakiho (谢谢!)的答案不是完整的解决方案,但它帮助我找到了解决方案。 我认为问题在于@Binding var todo: TodoItem
in TodoRow
。 我最终传递了我需要的所有属性而不是整个TodoItem
并且它正在工作。
如果有人遇到同样的问题,这里是完整的工作代码:
struct TodoRow: View {
@State var buttonHover: Bool = false
var name: String
var isCompleted: Bool
var toggleItem: () -> Void
var removeItem: () -> Void
var body: some View {
VStack(alignment: .leading) {
HStack {
Button(action: toggleItem) {
Image(nsImage: NSImage(named: NSImage.Name(NSImage.menuOnStateTemplateName))!)
.resizable()
.frame(width: 8, height: 8)
.padding(3)
.opacity(isCompleted ? 1 : buttonHover ? 0.5 : 0)
}.buttonStyle(PlainButtonStyle())
.background(Capsule().stroke(Color.primary, lineWidth: 1))
.onHover(perform: { val in self.buttonHover = val })
Text("\(name)").strikethrough(isCompleted, color: Color.primary)
}.opacity(isCompleted ? 0.35 : 1)
Divider().fixedSize(horizontal: false, vertical: true).frame(height: 1)
}.contextMenu {
Button(action: removeItem) {
Text("Delete")
}
}
}
}
struct TodoList: View {
var listName: String
@State var newTodo: String = ""
@State var todos: [TodoItem] = []
@State var showCompleted = false
func addTodo() {
let trimmedTodo = newTodo.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedTodo.isEmpty {
todos.insert(TodoItem(name: trimmedTodo), at: 0)
newTodo = ""
}
}
var body: some View {
return VStack(alignment: .leading) {
Text("\(listName)").font(.headline)
HStack {
TextField("New todo...", text: $newTodo)
NativeButton("Add", keyEquivalent: .return) {
self.addTodo()
}
}
List {
ForEach(todos.indices.filter { self.showCompleted || !todos[$0].isCompleted }, id: \.self) { index in
TodoRow(
name: self.todos[index].name,
isCompleted: self.todos[index].isCompleted,
toggleItem: {
self.$todos[index].isCompleted.wrappedValue.toggle()
},
removeItem: {
self.todos.remove(at: index)
}
)
}.onDelete { offsets in
self.todos.remove(atOffsets: offsets)
}
}
Toggle(isOn: $showCompleted) {
Text("Show completed")
}
}.padding().frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
}
你好,我注意到您正在过滤索引,我检查了删除时的偏移量,我发现有时偏移量范围与实际索引不同,我尝试更改一些内容:
List {
ForEach(todos.filter { self.showCompleted || !$0.isCompleted }, id: \.self) { item in
TodoRow(todo: item)
}.onDelete{offsets in
self.todos.remove(atOffsets: offsets)
}
}
并且这种方式需要使TodoItem
符合 'Hashable' -> struct TodoItem: Identifiable, Hashable
所以试一试,如果它不能解决你的问题,请回击我
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.