【问题标题】:SwiftUI - removing item from list crashes with Fatal error: Index out of rangeSwiftUI - 从列表中删除项目崩溃并出现致命错误:索引超出范围
【发布时间】:2020-03-27 06:49:30
【问题描述】:

我对 SwiftUI 中的 List 有疑问。从列表中删除任何项目都会崩溃,并出现错误 Fatal error: Index out of range。使用onDelete 方法不起作用,并且在我的自定义函数中不起作用。我做错了什么?

这是 ma​​cOS 应用程序,而不是 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 的新手。

【问题讨论】:

标签: swift macos list swiftui


【解决方案1】:

所以 fakiho 的答案(谢谢!)不是完整的解决方案,但它帮助我找到了解决方案。我认为问题在于TodoRow 中的@Binding var todo: TodoItem。我最终传递了我需要的所有属性而不是整个 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)
    }
}

【讨论】:

    【解决方案2】:

    您好,我注意到您正在过滤索引,我检查了删除时的偏移量,我发现有时偏移量范围与实际索引不同,我尝试更改一些内容:

          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

    所以试一试,如果它没有解决你的问题,请回击我

    【讨论】:

    • 不幸的是它不起作用。它甚至不编译。 ForEach(todos.filter { self.showCompleted || !$0.isCompleted }, id: \.self) { item inbody 的第一行导致错误 Static member 'leading' cannot be used on instance of type 'HorizontalAlignment',即使没有过滤器 (ForEach(todos, id: \.id) { item in)。如果我删除 (alignment: .leading) 它会在下一行给我一个错误:Type of expression is ambiguous without more context...
    • 有时当编译器给出与你类似的错误是因为组件没有接受所有参数..确保你删除了 TodoRow 中所需的变量,如 index 和 removeTodo 并打我返回
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-15
    相关资源
    最近更新 更多