SwiftUI+CoreData导入CSV报错:Foundation._GenericObjCError error 0求助
SwiftUI CSV导入CoreData时出现Foundation._GenericObjCError error 0错误
我在SwiftUI项目中实现CSV导入功能,点击「Import CSV」按钮后,用户可选择文件并将内容导入保存至CoreData实体。但运行应用时,选择文件后虽能显示文件名和「File Read Successfully」提示,却出现错误:Error the operation could not be completed. (Foundation._GenericObjCError error 0.)
相关代码
import SwiftUI import CoreData import UniformTypeIdentifiers struct CSVImportView: View { @State private var document: MessageDocument = MessageDocument(message: "Hello, World!") @State private var fileContent: String = "" @State private var errorMessage: String = "" @State private var showAlert: Bool = false @Environment(\.managedObjectContext) private var moc @State private var isImporting: Bool = false @State private var file: URL? var body: some View { NavigationView { VStack { Button("Import CSV") { isImporting.toggle() } if let file = file { Text("Selected File: \(file.lastPathComponent)") Text(fileContent.isEmpty ? "Cannot read file" : "File Read Successfully") .foregroundColor(fileContent.isEmpty ? .red : .green) GroupBox(label: Text("Message:")) { TextEditor(text: $document.message) } } } .padding() .navigationTitle("CSV Import") } .fileImporter( isPresented: $isImporting, allowedContentTypes: [.plainText], allowsMultipleSelection: false ) { result in do { guard let selectedFile = try result.get().first else { return } print("Selected File URL:", selectedFile) guard selectedFile.startAccessingSecurityScopedResource() else { showAlertWithError(message: "Unable to access the selected file.") return } defer { selectedFile.stopAccessingSecurityScopedResource() } self.file = selectedFile let fileContentData: Data do { fileContentData = try Data(contentsOf: selectedFile) } catch let error { showAlertWithError(message: "Error reading file content: \(error.localizedDescription)") return } print("File Content:", fileContentData) guard let message = String(data: fileContentData, encoding: .utf8) else { showAlertWithError(message: "Unable to read file content.") return } document.message = message fileContent = message let parsedData = parseCSV(content: message) let container = NoteCoreData.shared.persistentContainer saveToCoreData(parsedData: parsedData, with: container) { success, message in if success { showAlert = true errorMessage = message } else { showAlert = true errorMessage = message print("Error saving data:", message) } } print("Parsed Data:", parsedData) } catch let fileError as NSError { print("File error:", fileError.localizedDescription) showAlertWithError(message: "File Error: \(fileError.localizedDescription)") } catch { showAlertWithError(message: error.localizedDescription) } } .alert(isPresented: $showAlert) { Alert(title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("OK"))) } } func importCSV(url: URL) { do { let content = try String(contentsOf: url, encoding: .utf8) let parsedData = parseCSV(content: content) let container = NoteCoreData.shared.persistentContainer saveToCoreData(parsedData: parsedData, with: container) { success, message in if success { showAlert = true errorMessage = message } else { showAlert = true errorMessage = message print("Error saving data:", message) } } } catch { showAlertWithError(message: error.localizedDescription) } } func parseCSV(content: String) -> [Note] { var parsedData: [Note] = [] let rows = content.components(separatedBy: "\n") let possibleDateFormats = ["MM/dd/yyyy", "dd/MM/yyyy", "yyyy-MM-dd"] for row in rows { let columns = row.components(separatedBy: ",") if columns.count >= 3 { let title = columns[0] let dateString = columns[1] let noteContent = columns[2] if let date = tryFixingDate(dateString, possibleDateFormats: possibleDateFormats) { let note = Note() note.title = title note.date = date note.noteContent = noteContent parsedData.append(note) } else { print("Unable to convert date:", dateString) } } else if !row.isEmpty { print("Incomplete row:", row) } } return parsedData } func saveToCoreData(parsedData: [Note], with container: NSPersistentContainer, completion: @escaping (Bool, String) -> Void) { let context = container.viewContext for note in parsedData { let newNote = Note(context: context) newNote.userID = "UniqueUserID" newNote.id = UUID() newNote.title = note.title newNote.date = note.date newNote.noteContent = note.noteContent newNote.dataType = "Note" } do { try context.save() completion(true, "Data saved successfully!") } catch { completion(false, error.localizedDescription) } } func tryFixingDate(_ dateString: String, possibleDateFormats: [String]) -> Date? { let dateFormatter = DateFormatter() for format in possibleDateFormats { dateFormatter.dateFormat = format if let parsedDate = dateFormatter.date(from: dateString) { return parsedDate } } return nil } func showAlertWithError(message: String) { errorMessage = message showAlert = true print("Error:", message) } } struct MessageDocument: FileDocument { static var readableContentTypes: [UTType] { [.plainText] } var message: String init(message: String) { self.message = message } init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents, let string = String(data: data, encoding: .utf8) else { throw CocoaError(.fileReadCorruptFile) } message = string } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { return FileWrapper(regularFileWithContents: message.data(using: .utf8)!) } }
问题原因及修复方案
核心问题
错误Foundation._GenericObjCError error 0的直接原因是在parseCSV函数中直接调用let note = Note()创建CoreData实体实例。CoreData实体必须通过指定的NSManagedObjectContext初始化,空构造器创建的实例没有关联上下文,后续在saveToCoreData中操作会导致上下文状态异常。
修复步骤
定义纯数据结构体存储解析结果
创建一个与CoreData实体结构匹配的结构体,临时存储CSV解析后的数据,避免提前关联CoreData上下文:struct ParsedNote { let title: String let date: Date let noteContent: String }修改
parseCSV函数返回纯数据
调整解析逻辑,返回上述结构体数组,不再直接创建CoreData实体:func parseCSV(content: String) -> [ParsedNote] { var parsedData: [ParsedNote] = [] let rows = content.components(separatedBy: "\n") let possibleDateFormats = ["MM/dd/yyyy", "dd/MM/yyyy", "yyyy-MM-dd"] for row in rows { let trimmedRow = row.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedRow.isEmpty else { continue } let columns = trimmedRow.components(separatedBy: ",") guard columns.count >= 3 else { print("不完整的行:", trimmedRow) continue } let title = columns[0].trimmingCharacters(in: .whitespacesAndNewlines) let dateString = columns[1].trimmingCharacters(in: .whitespacesAndNewlines) let noteContent = columns[2].trimmingCharacters(in: .whitespacesAndNewlines) if let date = tryFixingDate(dateString, possibleDateFormats: possibleDateFormats) { let parsedNote = ParsedNote(title: title, date: date, noteContent: noteContent) parsedData.append(parsedNote) } else { print("无法转换日期:", dateString) } } return parsedData }更新
saveToCoreData函数参数
接收纯数据结构体数组,在函数内部创建CoreData实体并关联上下文:func saveToCoreData(parsedData: [ParsedNote], with container: NSPersistentContainer, completion: @escaping (Bool, String) -> Void) { let context = container.viewContext context.perform { // 在上下文队列中执行操作,避免线程问题 for note in parsedData { let newNote = Note(context: context) newNote.userID = "UniqueUserID" newNote.id = UUID() newNote.title = note.title newNote.date = note.date newNote.noteContent = note.noteContent newNote.dataType = "Note" } do { try context.save() DispatchQueue.main.async { completion(true, "数据保存成功!") } } catch { context.rollback() // 保存失败时回滚上下文,清理无效实体 DispatchQueue.main.async { completion(false, "保存失败: \(error.localizedDescription)") } print("保存数据错误:", error) } } }优化文件读取逻辑
直接使用String(contentsOf:encoding:)读取文件,减少不必要的Data转换:// 替换原文件读取代码 guard let message = try? String(contentsOf: selectedFile, encoding: .utf8) else { showAlertWithError(message: "无法读取文件内容") return } document.message = message fileContent = message
内容的提问来源于stack exchange,提问作者Kinetic Network
相关产品推荐
相关产品推荐

