When using SwiftUI to create a document based app, the default document type is to subclass FileDocument.
All examples lead to simple value types to be used in this document type.
I'm looking to create a UIManagedDocument in SwiftUI but there doesn't seem to be any mention of using FileDocument with core data. I noticed a ReferenceFileDocument but this leads to no examples either...
Has anyone had any experience of using either SwiftUI document type for core data based documents?
After some more months, I came across this question once again.
Since my last comment on September 18th, I've worked myself on solving the puzzle of building a SwiftUI document-based app using Core Data.
Looking more in-depth I learned that the
UIManagedDocument(respectively its parentUIDocument) infrastructure is really close/similar to what SwiftUI tries to implement. SwiftUI even usesUIDocumentin the background to do "its magic".UIDocumentandUIManagedDocumentare simply some more archaic remnants of times where Objective-C was the dominant language. There was no Swift and there were no value-types.In general I can give you the following tips to solve your challenge using Core Data within a
UIManagedDocument:UTTypewill have to conform to.package(=com.apple.packagein your Info.plist file). You won't be able to make Core Data work with only a plain file document.use a
ReferenceFileDocumentbased class to build a wrapper document for yourUIManagedDocument. This is necessary because you will have to know about the time when the object is released. Indeinityou will have to callmanagedDocument.close()to ensure theUIManagedDocumentis properly closed and no data is lost.the required function
init(configuration:)is going to be called when an existing document is opened. Sadly, it is of no use when working withUIManagedDocumentbecause we have only access to theFileWrapperof the document and not theURL. But theURLis what you need to initialize theUIManagedDocument.the required function
snapshot(contentType:)andfileWrappper(snapshot:, configuration:)is only used to create a new empty document. (This is because we won't use the SwiftUI integratedUndoManagerbut the one fromUIManagedDocumentrespectively Core DatasNSManagedObjectContext.) Therefore it is not relevant what your type forSnapshotis. You can use aDateorIntbecause the snapshot taken with the first function is not what you are going to write in the second function.The
The
fileWrappper(snapshot:, configuration:)function should return the file structure of an emptyUIManagedDocument. This means, it should contain a directoryStoreContentand an empty file with the filename of the persistent store (default ispersistentStore) as in the screenshot below.persistentStore-shmandpersistentStore-walfiles are going to be created automatically when Core Data is starting up, so we do not have to create them in advance.I am using the following expression to create the
FileWrapperrepresenting the document: (MyManagedDocumentis myUIManagedDocumentsubclass)UIManagedDocumentsubclass, because we have no idea where the document (represented by theFileWrapperwe have created) is located. Luckily SwiftUI is passing us theURLof the currently opened document in theReferenceFileDocumentConfigurationwhich is accessible in theDocumentGroupcontent closure. The propertyfileURLcan then be used to finally create and open ourUIManagedDocumentinstance from the wrapper. I'm doing this as follows: (file.documentis an instance of ourReferenceFileDocumentclass)in my
open(fileURL:)method, I then instantiate theUIManagedDocumentsubclass and callopento properly initialize it.With above steps you will be able to display your document and access its
managedObjectContextin a view similar to this: (DocumentViewis a regular SwiftUI view using for example@FetchRequestto query data)But you will soon encounter some issues/crashes:
You see the app freeze when a document is opened. It seems as if
UIManagedDocumentopenorclosewon't finish/return.This is due to some deadlock. (You might remember, that I initially told you that SwiftUI is using
UIDocumentbehind the scene? This is probably the cause of the deadlock: we are running already someopenwhile we try to execute anotheropencommand.Workaround: run all calls to
openandcloseon a background queue.Your app crashes when you try to open another document after having previously closed one. You might see errors as:
After having debugged this for some hours, I learned that the
NSManagedObjectModel(instantiated by ourUIManagedDocument) is not released between closing a document and opening another. (Which, by good reason, is not necessary as we would use the same model anyway for the next file we open). The solution I found to this problem was to override themanagedObjectModelvariable for myUIManagedDocumentsubclass and return theNSManagedObjectModelwhich I'm loading "manually" from my apps bundle. I suppose there are nicer ways to do this, but here is the code I'm using:So this answer has become really lengthy, but I hope it is helpful to others struggling with this topic. I've put up this gist with my working example to copy and explore.