Core Data一对多关系问题:无法创建关联笔记
问题描述
我是Core Data新手,正在开发一款简易笔记App,采用文件夹与笔记的一对多关系(一个文件夹可包含多条笔记)。此前能正常创建笔记,但设置一对多关系后,点击添加笔记图标无任何反应。我通过ObservedObject将选中的文件夹传递给NoteView,尝试筛选对应文件夹的笔记但未成功,怀疑是Core Data属性或逻辑存在错误,相关代码如下:
FolderView代码
struct Sidebar: View { @Environment(\.managedObjectContext) private var viewContext @State private var isPresented: Bool = false @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.folderName, ascending: true)]) var folders: FetchedResults<Folder> @State private var selectedFoldersIds: Set<Folder.ID> = [] private var selectedFolder: Folder?{ guard let selectedFolderId = selectedFoldersIds.first, let selectedFolder = folders.filter ({$0.id == selectedFolderId}).first else{ return nil } return selectedFolder } var body: some View { Button(){ isPresented = true } label: { HStack{ Image(systemName: "folder.badge.plus") Text("Add Folder") } .cornerRadius(50) } .sheet(isPresented: $isPresented){ AddFolderView{ name in saveNewFolder(name: name) } } .padding() Divider() Text("Folders") .frame(maxWidth: .infinity, alignment: .leading) .font(.caption) .foregroundColor(.secondary) List(folders, selection: $selectedFoldersIds){folder in HStack{ Image(systemName: "folder") NavigationLink(folder.folderName!, destination:NoteView(rootFolder: folder)) } } .onDeleteCommand(perform: deleteSelectedFolders) .toolbar{ ToolbarItem(placement: .primaryAction){ Button(action: toggleSidebar) { Image(systemName: "sidebar.left") } } ToolbarItem(placement: .primaryAction){ Button(action: deleteSelectedFolders){ Label("Delete note", systemImage: "trash") } } } } private func toggleSidebar() { NSApp.keyWindow?.firstResponder? .tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) } func saveNewFolder(name: String){ let folder = Folder(context: viewContext) folder.folderId = UUID() folder.folderName = name do{ try viewContext.save() } catch{ let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } private func deleteSelectedFolders(){ withAnimation{ let selectedFolders = folders.filter {selectedFoldersIds.contains($0.id)} deleteFolders(folders: selectedFolders) } } private func deleteFolders(folders: [Folder]){ viewContext.perform{ folders.forEach(viewContext.delete)} } }
NoteView代码
struct NoteView: View { @ObservedObject var rootFolder: Folder @Environment(\.managedObjectContext) private var viewContext @State private var isPresented: Bool = false @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)]) var notes: FetchedResults<Note> @State private var selectedNotesIds: Set<Note.ID> = [] private var selectedNote: Note?{ guard let selectedNoteId = selectedNotesIds.first, let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{ return nil } return selectedNote } var body: some View { List(rootFolder.notesArray, selection: $selectedNotesIds){ note in // is the problem here? VStack{ NavigationLink(note.name!, destination: NoteEditor(note: note)) HStack{ Image(systemName: "folder") Text(rootFolder.folderName!) } Divider() } } .toolbar{ ToolbarItem(placement: .primaryAction){ Button(){ isPresented.toggle() } label: { Image(systemName: "square.and.pencil") } .sheet(isPresented: $isPresented){ AddNoteView{ name in saveNewNote(name: name, text: "") } } } ToolbarItem(placement: .primaryAction){ Button(action: deleteSelectedNotes){ Label("Delete note", systemImage: "trash") } } } } func saveNewNote(name: String, text: String){ let note = Note(context: viewContext) note.id = UUID() note.name = name note.text = text do{ try viewContext.save() } catch{ let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } private func deleteSelectedNotes(){ withAnimation{ let selectedNotes = notes.filter {selectedNotesIds.contains($0.id)} deleteNotes(notes: selectedNotes) } } private func deleteNotes(notes: [Note]){ viewContext.perform{ notes.forEach(viewContext.delete)} } }
Folder+CoreDataProperties代码
extension Folder { @nonobjc public class func fetchRequest() -> NSFetchRequest<Folder> { return NSFetchRequest<Folder>(entityName: "Folder") } @nonobjc class func fetchRequest(forID folderId: UUID) -> NSFetchRequest<Folder> { let request = Folder.fetchRequest() request.predicate = NSPredicate(format: "id = %@", folderId as CVarArg) return request } @NSManaged public var folderId: UUID? @NSManaged public var folderName: String? @NSManaged public var notes: NSSet? public var notesArray: [Note] { let set = notes as? Set<Note> ?? [] return set.sorted { $0.wrappedName > $1.wrappedName } } }
Note+CoreDataProperties代码
extension Note { @nonobjc public class func fetchRequest() -> NSFetchRequest<Note> { return NSFetchRequest<Note>(entityName: "Note") } @nonobjc class func fetchRequest(forID id: UUID) -> NSFetchRequest<Note> { let request = Note.fetchRequest() request.predicate = NSPredicate(format: "id = %@", id as CVarArg) return request } @NSManaged public var id: UUID? @NSManaged public var name: String? @NSManaged public var text: String? @NSManaged public var folder: Folder? public var wrappedName: String { name ?? "folderName" } }
解决方案
1. 修复笔记创建时的关联逻辑
问题核心:创建笔记时没有将其关联到当前选中的文件夹,导致笔记不属于任何文件夹,所以rootFolder.notesArray里看不到新创建的笔记,看起来像是“点击添加无反应”。
修改NoteView中的saveNewNote方法,添加文件夹关联:
func saveNewNote(name: String, text: String){ let note = Note(context: viewContext) note.id = UUID() note.name = name note.text = text // 新增:将笔记关联到当前文件夹 note.folder = rootFolder do{ try viewContext.save() } catch{ let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } }
2. 优化选中笔记的逻辑(可选)
当前selectedNote是从全局的notes结果中筛选,可能包含不属于当前文件夹的笔记,建议改为从当前文件夹的笔记列表中筛选:
private var selectedNote: Note?{ guard let selectedNoteId = selectedNotesIds.first, let selectedNote = rootFolder.notesArray.filter ({$0.id == selectedNoteId}).first else{ return nil } return selectedNote }
3. 验证Core Data模型关系
确保Core Data模型中:
- Folder实体的
notes关系,目标是Note实体,类型为“To Many” - Note实体的
folder关系,目标是Folder实体,类型为“To One”,并且设置为notes关系的反向关系 - 建议给Folder的
notes关系设置删除规则为Cascade,这样删除文件夹时会自动删除关联的笔记
4. 优化NoteView的笔记列表(可选)
如果需要更高效的笔记查询,可以给@FetchRequest添加谓词,只获取当前文件夹的笔记,替换原来的全局notes请求:
// 替换原来的@FetchRequest @FetchRequest var notes: FetchedResults<Note> // 在初始化中设置谓词 init(rootFolder: Folder) { self.rootFolder = rootFolder _notes = FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)], predicate: NSPredicate(format: "folder == %@", rootFolder) ) } // 然后列表可以直接用notes: List(notes, selection: $selectedNotesIds){ note in // ... 现有代码 }
这样修改后,创建的笔记会正确关联到当前文件夹,列表也能实时显示新添加的笔记。
内容的提问来源于stack exchange,提问作者jari85
相关产品推荐
相关产品推荐

