适配iPhone和iPad的SwiftData NavigationSplitView删除最后项崩溃
问题场景
使用Xcode 15.4创建的SwiftData模板应用,通过NavigationSplitView适配iPhone和iPad。视图中List嵌套带.onDelete(perform:)的ForEach,删除数据库最后一项后点击编辑按钮的“完成”,应用崩溃,错误信息:
Attempted to modify the selection of an item in an out-of-bounds section
原代码如下:
import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] var body: some View { NavigationSplitView { List { ForEach(items) { item in NavigationLink { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } private func deleteItems(offsets: IndexSet) { withAnimation { for index in offsets { modelContext.delete(items[index]) do { try modelContext.save() } catch { print(error) } } } } } #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) }
问题原因
删除最后一项后,NavigationSplitView的主视图列表已无数据,但系统内部仍可能持有已删除项的选择状态。点击“完成”编辑时,系统尝试更新这个无效的选择,导致越界错误。
解决方案
手动管理列表的选中状态,删除后及时重置无效的选中项,避免系统操作无效数据。修改后的代码如下:
import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @State private var selectedItem: Item? // 手动管理选中状态 var body: some View { NavigationSplitView { // 给List绑定selection,明确控制选中项 List(selection: $selectedItem) { ForEach(items) { item in // 改用value形式的NavigationLink,配合List的selection NavigationLink(value: item) { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } detail: { // 根据选中状态展示详情 if let selectedItem { Text("Item at \(selectedItem.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } else { Text("Select an item") } } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } private func deleteItems(offsets: IndexSet) { withAnimation { // 先收集所有要删除的项 let itemsToDelete = offsets.map { items[$0] } // 批量删除 itemsToDelete.forEach { modelContext.delete($0) } // 如果选中的项在删除列表中,重置选中状态 if let selectedItem, itemsToDelete.contains(selectedItem) { self.selectedItem = nil } // 统一保存上下文,避免多次调用save do { try modelContext.save() } catch { print(error) } } } } #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) }
修改说明
- 新增
@State private var selectedItem: Item?,手动控制列表的选中状态 - List添加
selection: $selectedItem绑定,让系统明确选中项的来源 - NavigationLink改用
value形式,配合List的selection机制,更符合SwiftUI的状态管理逻辑 - 删除逻辑优化:先收集要删除的项,批量删除后检查选中项是否被删除,及时重置为nil
- 将
modelContext.save()移到循环外,减少数据库操作次数,提升效率
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

