You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

适配iPhone和iPad的SwiftData NavigationSplitView删除最后项崩溃

解决NavigationSplitView中删除SwiftData最后一项后点击编辑完成崩溃的问题

问题场景

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 08:30:16