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

Swift中如何修复“Attempt to delete section containing first responder...”错误?

问题:CoreData闪卡应用添加数据时崩溃

错误信息

Thread 1: "Attempt to delete section containing first responder that refused to resign.\nFirst responder that was asked to resign (returned YES from -resignFirstResponder): <UITextField: 0x130146000; frame = (0 0; 249 22); opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x600001df3750>; placeholder = Enter definition; borderStyle = None; background = <_UITextFieldNoBackgroundProvider: 0x6000010e3620: textfield=<UITextField 0x130146000>>; layer = <CALayer: 0x60000122c240>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x1341bec00; baseClass = UICollectionViewListCell; frame = (20 35; 353 361); clipsToBounds = YES; layer = <CALayer: 0x600001238320>> at index path: <NSIndexPath: 0x833f455114c3dfbc> {length = 2, path = 0 - 0}\nCurrent first responder: <TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier>: 0x13107e200; frame = (0 0; 353 361); autoresize = W+H; gestureRecognizers = <NSArray: 0x600001c333c0>; layer = <CALayer: 0x600001239ac0>> inside co"

问题场景

我用CoreData开发闪卡应用,添加数据阶段运行时出现上述崩溃。推测问题出在DataController的两个数组,或是点击“+”按钮时遍历dataController.termdefpairs的for循环。视图核心功能是添加闪卡数据创建卡组,点击“save”按钮会用CoreData保存上下文及所有数据。

相关代码

TermDefinitionView及关联视图

struct TermDefinitionView: View {

    @ObservedObject var dataController = DataController.shared
    @ObservedObject private var viewModel = TermDefinitionViewModel()
    @State var name = ""
    @State var term = ""
    @State var definition = ""
    @State var tag = ""// Separate state for the name
    @Environment(\.managedObjectContext) var managedObjectContext
    @Environment(\.dismiss) var dismiss
    @State var showSet = false
    //@Binding var saveSet: Bool
    @State var isShowingSet = false
    @State private var currentSet: SetEntity?
    //@Binding var redirectToSet: Bool
    
    var body: some View {
        ZStack {
            NavigationStack {
                VStack {
                    Text("Name")
                        .padding(.trailing, 220)
                        .fontWeight(.bold)
                    TextField("Enter name", text: $name)
                        .frame(width: 250)
                        .padding()
                        .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
                    
                    List {
                        
//                        ForEach($dataController.termdefpairs) { _ in
//                            TermView(term: term, definition: definition, tag: tag)
//
//                        }
                        
                        ForEach(dataController.termdefpairs.indices, id: \.self) { index in
                            TermView(term: dataController.termdefpairs[index].term, definition: dataController.termdefpairs[index].definition, tag: dataController.termdefpairs[index].tag)

                        }
                        .onDelete { index in
                            self.dataController.termdefpairs.remove(at: index.first!)
                        }
                    }
                    .navigationBarItems(trailing: Button(action: {
                        //for testForm in viewModel.termdefpairs {}
                        dataController.termdefpairs.removeAll()
                        dataController.save() //saving data
                        
                            
                    }) {
                        Text("Save")
//                        
                        
                        
                    })
                    
                    Spacer()
                
                    Button(action: {
                        dataController.addNew()
                        for pair in dataController.termdefpairs {
                            dataController.add(term: pair.term, definition: pair.definition, tag: pair.tag, date: Date(), name: name)
                        }
                        name = ""
                        
                        isShowingSet = true
                        dismiss()
                    }) {
                        Image(systemName: "plus")
                            .resizable()
                            .foregroundColor(.white)
                            .font(.title2)
                            .frame(width: 30, height: 30)
                    }
                    .frame(width: 40, height: 40)
                    .background(Color.blue)
                    .clipShape(Circle())
                    .padding()
                }
                
            }
        }
    
    }
}


struct TermDefinitionView_Previews: PreviewProvider {
    static var previews: some View {
        TermDefinitionView()
    }
}

struct TermView: View {
    @State var term: String
    @State var definition: String
    @State var tag: String
    @State private var isTagExpanded = false
    
    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            
            
                if isTagExpanded {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("Tag")
                            .font(.headline)
                        TextField("Enter tag", text: $tag)
                            .padding()
                            .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
                    }
                }
                
                Button(action: {
                    isTagExpanded.toggle()
                }) {
                    HStack { 
                        Spacer()
                        Image(systemName: isTagExpanded ? "minus.circle.fill" : "plus.circle.fill")
                            .resizable()
                            .foregroundColor(.blue)
                            .frame(width: 25, height: 25)
                        Text(isTagExpanded ? "Hide Tag" : "Add Tag")
                            .foregroundColor(.blue)
                            .font(.headline)
                    }
                }
                .padding(.bottom, -25)
            VStack(alignment: .leading, spacing: 8) {
                Text("Term")
                    .font(.headline)
                TextField("Enter term", text: $term)
                    .padding()
                    .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
            }
            
            VStack(alignment: .leading, spacing: 8) {
                Text("Definition")
                    .font(.headline)
                TextField("Enter definition", text: $definition)
                    .padding()
                    .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
            }
            
            
        }
        .padding()
    }
}



struct TermAndDefinition: Identifiable {
    var id = UUID()
    var term: String
    var definition: String
    var tag: String
}

DataController代码

class DataController:  ObservableObject {
    static let shared = DataController()
    @Published var termdefpairs: [TermAndDefinition] = []
    @Published var savedFlash: [FlashCardData] = []
   
    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "CoreData")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
       // super.init()
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
            self.container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
        })
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        fetchRequest()
    }
    func addNew() {
        termdefpairs.append(TermAndDefinition(term: "", definition: "", tag: ""))
//        func add(term: String, definition: String, tag: String, date: Date, name: String) {
//            let data = FlashCardData(context: self.container.viewContext)
//            data.id = UUID()
//            data.definition = definition
//            data.term = term
//            data.tag = tag
//            data.date = date
//            data.name = name
//            fetchRequest()
//        }
    }
    func fetchRequest() {
        let request: NSFetchRequest<FlashCardData> = FlashCardData.fetchRequest()
                let sort = NSSortDescriptor(keyPath: \FlashCardData.date, ascending: false)
                request.sortDescriptors = [sort]

                do {
                    self.savedFlash = try container.viewContext.fetch(request)
                } catch {
                    print("Failed to fetch FlashCardData: \(error)")
                }
    }
    

    func save() {
        do {
            try container.viewContext.save()
            print("Data saved")
        } catch {
            print("We could not save the data...")
        }
    }

    func add(term: String, definition: String, tag: String, date: Date, name: String) {
        let data = FlashCardData(context: self.container.viewContext)
        data.id = UUID()
        data.definition = definition
        data.term = term
        data.tag = tag
        data.date = date
        data.name = name
        fetchRequest()
    }

CoreData结构

CoreData结构


解决方案

1. 崩溃原因

错误本质是:操作列表(清空/删除元素)时,列表内的UITextField仍处于编辑状态(第一响应者),SwiftUI尝试让其取消编辑状态时出现异常,触发崩溃。

2. 具体修复步骤

(1)修复TextField绑定同步问题

当前TermView的参数是值传递,修改输入框内容不会同步回dataController.termdefpairs,还会导致状态混乱,需改为绑定传递:

// 修改TermView参数为Binding类型
struct TermView: View {
    @Binding var term: String
    @Binding var definition: String
    @Binding var tag: String
    @State private var isTagExpanded = false
    
    // 其余视图代码保持不变
}

// 在TermDefinitionView的List中使用绑定式ForEach
List {
    ForEach($dataController.termdefpairs) { $pair in
        TermView(term: $pair.term, definition: $pair.definition, tag: $pair.tag)
    }
    .onDelete { indexSet in
        dataController.termdefpairs.remove(atOffsets: indexSet)
    }
}

(2)操作数组前强制取消键盘编辑

在清空/删除数组前,手动让当前第一响应者取消编辑:

// 添加全局取消键盘的工具方法
extension View {
    func resignKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

// 修改Save按钮的action
Button(action: {
    resignKeyboard()
    dataController.termdefpairs.removeAll()
    dataController.save()
}) {
    Text("Save")
}

// 修改+按钮的action
Button(action: {
    resignKeyboard()
    dataController.addNew()
    // 注意:原逻辑每次点击+都会重复保存所有已有数据,建议移到Save按钮统一保存
    for pair in dataController.termdefpairs {
        dataController.add(term: pair.term, definition: pair.definition, tag: pair.tag, date: Date(), name: name)
    }
    name = ""
    isShowingSet = true
    dismiss()
}) {
    // 按钮样式代码不变
}

(3)修复重复存储逻辑

原+按钮的遍历保存逻辑会导致重复存储已有数据,建议将批量保存逻辑移到Save按钮:

// Save按钮最终action
Button(action: {
    resignKeyboard()
    // 批量保存所有闪卡数据
    for pair in dataController.termdefpairs {
        dataController.add(term: pair.term, definition: pair.definition, tag: pair.tag, date: Date(), name: name)
    }
    dataController.save()
    dataController.termdefpairs.removeAll()
    name = ""
    isShowingSet = true
    dismiss()
}) {
    Text("Save")
}

// +按钮仅负责添加空输入项
Button(action: {
    resignKeyboard()
    dataController.addNew()
}) {
    // 按钮样式代码不变
}

3. 额外优化建议

  • 避免用ForEach(indices)遍历数组,这种方式在数组元素变动时易出现状态不同步,优先使用绑定式ForEach($array)。
  • 确保DataController单例使用正确,当前TermDefinitionView中直接引用DataController.shared的方式是正确的。

内容的提问来源于stack exchange,提问作者Kolea

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:55:54