带有 UIManagedDocument/核心数据的 FileDocument

Ada*_*ter 7 core-data uimanageddocument swiftui

使用 SwiftUI 创建基于文档的应用程序时,默认文档类型是子类FileDocument.

所有示例都会导致在此文档类型中使用简单的值类型。

我正在寻找UIManagedDocument在 SwiftUI 中创建一个,但似乎没有提到使用FileDocument核心数据。我注意到了,ReferenceFileDocument但这也没有任何例子......

有人有过使用 SwiftUI 文档类型作为基于核心数据的文档的经验吗?

pd9*_*d95 11

又过了几个月,我再次遇到这个问题。
自从 9 月 18 日发表最后评论以来,我一直致力于解决使用 Core Data 构建基于 SwiftUI 文档的应用程序的难题。

更深入地观察,我了解到UIManagedDocument(分别是其父级UIDocument)基础架构与 SwiftUI 尝试实现的非常接近/相似。UIDocumentSwiftUI 甚至在后台使用来发挥“它的魔力”。UIDocument它们UIManagedDocument只是 Objective-C 占主导地位的时代的一些更古老的残余。没有 Swift,也没有值类型。

一般来说,我可以为您提供以下提示来解决您在以下环境中使用核心数据所面临的挑战UIManagedDocument

  • 首先,如果您想使用 Core Data,则必须使用基于包/捆绑的文档格式。这意味着您UTType必须遵守.package(=com.apple.package在您的 Info.plist 文件中)。您将无法仅使用纯文件文档来使 Core Data 工作。
extension UTType {
    static var exampleDocument: UTType {
        UTType(exportedAs: "com.example.mydocument", conformingTo: .package)
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 使用ReferenceFileDocument基于类为您的UIManagedDocument. 这是必要的,因为您必须知道释放对象的时间。您deinit必须致电managedDocument.close()以确保UIManagedDocument正确关闭并且没有数据丢失。

  • init(configuration:)打开现有文档时将调用所需的函数。遗憾的是,它在使用时没有用UIManagedDocument,因为我们只能访问FileWrapper文档的 ,而不能访问URL. 但这URL是您初始化UIManagedDocument.

  • 所需的功能snapshot(contentType:)fileWrappper(snapshot:, configuration:)仅用于创建新的空文档。(这是因为我们不会使用集成的 SwiftUI UndoManager,而是使用UIManagedDocument分别来自 Core Datas 的SwiftUI NSManagedObjectContext。)因此,它与您的类型无关Snapshot。您可以使用Dateor ,Int因为使用第一个函数拍摄的快照不是您要在第二个函数中编写的内容。

  • fileWrappper(snapshot:, configuration:)函数应返回空的文件结构UIManagedDocument。这意味着,它应该包含一个目录StoreContent和一个空文件,其文件名是持久存储(默认为persistentStore),如下面的屏幕截图所示。 包装内容截图 和文件将在 Core Data 启动时自动persistentStore-shm创建persistentStore-wal,因此我们不必提前创建它们。
    我使用以下表达式来创建FileWrapper表示文档:(MyManagedDocument是我的UIManagedDocument子类)

FileWrapper(directoryWithFileWrappers: [
    "StoreContent" : FileWrapper(directoryWithFileWrappers: [
        MyManagedDocument.persistentStoreName : FileWrapper(regularFileWithContents: Data())
    ])
])
Run Code Online (Sandbox Code Playgroud)
  • 上述步骤允许我们创建一个空文档。但它仍然无法连接到我们的UIManagedDocument子类,因为我们不知道文档(由我们FileWrapper创建的表示)位于哪里。幸运的是,SwiftUI 向我们传递了URL当前打开的文档,ReferenceFileDocumentConfiguration可以在内容闭包中访问该文档DocumentGroup。然后,该属性fileURL可用于最终UIManagedDocument从包装器创建并打开我们的实例。我这样做如下:(file.document是我们班级的一个实例ReferenceFileDocument
DocumentGroup(newDocument: { DemoDocument() }) { file in
    ContentView()
        .onAppear {
            if let url = file.fileURL {
                file.document.open(fileURL: url)
            }
        }
}
Run Code Online (Sandbox Code Playgroud)
  • 在我的open(fileURL:)方法中,然后实例UIManagedDocument化子类并调用open以正确初始化它。

  • 通过上述步骤,您将能够显示您的文档并managedObjectContext在类似于此的视图中访问它:(DocumentView是一个常规的 SwiftUI 视图,例如用于@FetchRequest查询数据)

struct ContentView: View {
    @EnvironmentObject var document: DemoDocument

    var body: some View {
        if let managedDocument = document.managedDocument {
            DocumentView()
                .environment(\.managedObjectContext, managedDocument.managedObjectContext)
        } else {
            ProgressView("Loading")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但你很快就会遇到一些问题/崩溃:

  1. 打开文档时您会看到应用程序冻结。似乎UIManagedDocument openclose不会完成/返回。
    这是由于一些僵局造成的。UIDocument(您可能还记得,我最初告诉您 SwiftUI在幕后使用?这可能是死锁的原因:open当我们尝试执行另一个open命令时,我们已经在运行一段时间了。
    解决方法:openclose后台运行所有调用队列。

  2. 当您在之前关闭一个文档后尝试打开另一个文档时,您的应用程序会崩溃。您可能会看到如下错误:

warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Item' so +entity is unable to disambiguate.
warning:     'Item' (0x6000023c4420) from NSManagedObjectModel (0x600003781220) claims 'Item'.
warning:     'Item' (0x6000023ecb00) from NSManagedObjectModel (0x600003787930) claims 'Item'.
error: +[Item entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
Run Code Online (Sandbox Code Playgroud)

经过几个小时的调试后,我了解到NSManagedObjectModel(由我们实例化的UIManagedDocument)在关闭一个文档和打开另一个文档之间不会被释放。(出于充分的理由,这是不必要的,因为无论如何我们都会在打开的下一个文件中使用相同的模型)。我发现这个问题的解决方案是覆盖managedObjectModel我的UIManagedDocument子类的变量并返回NSManagedObjectModel我从应用程序包“手动”加载的变量。我想有更好的方法可以做到这一点,但这是我正在使用的代码:

warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Item' so +entity is unable to disambiguate.
warning:     'Item' (0x6000023c4420) from NSManagedObjectModel (0x600003781220) claims 'Item'.
warning:     'Item' (0x6000023ecb00) from NSManagedObjectModel (0x600003787930) claims 'Item'.
error: +[Item entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
Run Code Online (Sandbox Code Playgroud)

所以这个答案变得非常冗长,但我希望它对其他在这个话题上苦苦挣扎的人有所帮助。我已经将这个要点与我的工作示例一起复制和探索。

  • 你已经做了大量的研究!谢谢你!我寻找答案已经有一段时间了。不幸的是,这只能证明我的猜测:SwiftUI 还没有为 UIManagedDocument 或 NSPersistentDocument 做好准备。 (2认同)