SwiftData将添加到树状结构中某个子文件夹中的项,只有在重新启动应用后才会显示

编程语言 2026-07-11

我的SwiftData应用包含两个主要的模型类,形成一棵树,Folder(根节点和子节点)和Entry(叶节点),我在一个List中展示——这一切都按预期工作。

要添加新项,我使用AddWindow,其中有一个按钮用于向根文件夹添加新项,另一个按钮用于向子文件夹添加新项。

向根文件夹添加项可以正常工作。

向子文件夹添加的项没有在列表中显示

事实是,如果我退出应用并重新启动,将seedItems的标志设为false以不覆盖数据,那么在上次启动中向子文件夹添加的项现在可见。所以它们已经创建并插入到上下文中,但并未立即显示。

编辑:如果我把添加按钮移到主窗口,问题就解决了。所以这与AddWindowcontext有关。

我该如何解决?

一个最小可复现示例(MRE)如下:

SwiftData 模型:

import SwiftData

@Model
final class Folder {
    var name: String

    // Parent
    var parentFolder: Folder?

    // Child folders
    @Relationship(deleteRule: .cascade, inverse: \Folder.parentFolder)
    var subfolders: [Folder] = []

    // Leaf entries
    @Relationship(deleteRule: .cascade, inverse: \Entry.folder)
    var entries: [Entry] = []

    init(name: String, parentFolder: Folder? = nil) {
        self.name = name
        self.parentFolder = parentFolder
    }
}

@Model
final class Entry {
    var name: String

    // Parent
    var folder: Folder? // Inverse relationship pointing to Folder.entries

    init(name: String, folder: Folder? = nil) {
        self.name = name
        self.folder = folder
    }
}

下面是 AppTestView 的代码:

import SwiftData
import SwiftUI

@main
struct TestMacApp: App {
    var body: some Scene {
        WindowGroup {
            TestView()
                .modelContainer(for: Folder.self)
        }
        Window("Add", id: "openWindow") {
            AddWindow()
                .modelContainer(for: Folder.self)
        }
    }
}

struct TestView: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(\.openWindow) private var openWindow

    @Query(filter: #Predicate<Folder> { $0.parentFolder == nil })
    private var rootFolders: [Folder]

    var seedItems = true

    var body: some View {
        NavigationSplitView {
            VStack {
                List {
                    ForEach(rootFolders) { folder in
                        FolderRow(folder: folder)
                    }
                }
                .listStyle(.sidebar)
                .onAppear {
                    if seedItems {
                        seed()
                    }
                }

                Button {
                    openWindow(id: "openWindow")
                } label: {
                    Image(systemName: "plus")
                }
            }
        } detail: {
            Text("detail")
        }
    }

    func seed() {
        do {
            let descriptor = FetchDescriptor<Folder>()
            let folders: [Folder] = try modelContext.fetch(descriptor)

            for folder in folders {
                modelContext.delete(folder)
            }

            let rootFolder = Folder(name: "Root")

            let entry1 = Entry(name: "One")
            let entry2 = Entry(name: "Two")

            rootFolder.entries.append(contentsOf: [entry1, entry2])

            let subFolder = Folder(name: "Sub", parentFolder: rootFolder)

            let entry3 = Entry(name: "Three")
            let entry4 = Entry(name: "Four")

            subFolder.entries.append(contentsOf: [entry3, entry4])

            modelContext.insert(rootFolder)

            try modelContext.save()
        }
        catch {
            print(error)
        }
    }
}

struct FolderRow: View {
    var folder: Folder

    @State private var isExpanded: Bool = true

    var body: some View {
        DisclosureGroup(isExpanded: $isExpanded) {
            // Subfolders
            ForEach(folder.subfolders.sorted { $0.name < $1.name }) { subfolder in
                FolderRow(folder: subfolder)
            }

            // Entries (leaf nodes)
            ForEach(folder.entries.sorted { $0.name < $1.name }) { entry in
                NavigationLink {
                    Text(entry.name)
                } label: {
                    EntryRow(entry: entry)
                }
            }

        } label: {
            Label(folder.name, systemImage: "folder")
        }
    }
}

struct EntryRow: View {
    var entry: Entry

    var body: some View {
        Text(entry.name)
    }
}

下面是 AddWindow 的代码:

struct AddWindow: View {
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        Button("Add to Root") {
            addToRoot()
        }
        .padding()

        Button("Add to Sub") {
            addToSub()
        }
        .padding()
    }

    func addToRoot() {
        do {
            let entry = Entry(name: "New")

            let descriptor = FetchDescriptor<Folder>()
            let folders: [Folder] = try modelContext.fetch(descriptor)

            for folder in folders {
                if folder.name == "Root" {
                    folder.entries.append(entry)
                }
            }
            try modelContext.save()
        }
        catch {
            print(error)
        }
    }

    func addToSub() {
        do {
            let entry = Entry(name: "New")

            let descriptor = FetchDescriptor<Folder>()
            let folders: [Folder] = try modelContext.fetch(descriptor)

            for folder in folders {
                if folder.name == "Sub" {
                    folder.entries.append(entry)
                }
            }
            try modelContext.save()
        }
        catch {
            print(error)
        }
    }
}

解决方案

Ok, I think found the solution here:

在macOS应用中使用SwiftData打开带有动态数据的新窗口

我应该使用一个 sharedModelContainer,并将其注入到两个窗口中:

@main
struct TestMacApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([Folder.self])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            TestView()
                .modelContainer(sharedModelContainer)
        }
        Window("Add", id: "openWindow") {
            AddWindow()
                .modelContainer(sharedModelContainer)
        }
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章