Currently, I am developing a small document based App for iOS with SwiftUI. I was quite surprised to see that there is no obvious way to save the document’s contents to file after editing. In fact, the default behaviour was that nothing was saved at all after editing my document’s content. On macOS there is at least the possibility to update the change count to force a save later but that is not available on iOS SwiftUI.
Well, there is this hint on stackoverflow to register an undo at the UndoManager
even if you do not use it. For some Views that worked for me; for others it didn’t. I was getting tired to spend hours in investigating why this was so. So I was looking for a way to save the document’s content explicitly. This is what I came up with.
In my app the document’s content (my model) is encoded as a PropertyList
. So why not writing this PropertyList
by myself? In MyDocument
I create my model and keep a reference on it. In my model I store the fileURL
of my document’s file on creating or opening a document:
1 2 3 4 5 6 7 8 9 10 11 12 |
@main struct MyApp: App { var body: some Scene { DocumentGroup(newDocument: MyDocument()) { file in ContentView(document: file.$document) .environmentObject(file.document.model) .task { file.document.model.fileURL = file.fileURL } } } } |
So I can access this URL in my model:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func save() { if let url = fileURL { // we know our url if url.startAccessingSecurityScopedResource() { // access file outside sandbox if let outStream = OutputStream(url: url, append: false), // create the output stream let data = try? PropertyListEncoder().encode(self) { // and encode ourself as a property list outStream.open() // open the output stream let _ = outStream.write(data) // and write the property list outStream.close() // close the output stream } url.stopAccessingSecurityScopedResource() // stop secure access } } } |
The only problem with this is that outStream.write
is not that simple as it looks here. It needs some kind of buffer pointer which is not that easy to create in Swift 5. But here is this nice extension which does the job for me:
1 2 3 4 5 6 7 8 9 |
extension OutputStream { // from: https://stackoverflow.com/questions/55829812/writing-data-to-an-outputstream-with-swift-5 func write(_ data: Data) -> Int { return data.withUnsafeBytes({ (rawBufferPointer: UnsafeRawBufferPointer) -> Int in let bufferPointer = rawBufferPointer.bindMemory(to: UInt8.self) return self.write(bufferPointer.baseAddress!, maxLength: data.count) }) } } |
Now I can save the content of my document wherever I want it without using any dummy undo manager.