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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:35:24