I'm trying to use -[UIDocument saveToURL:(NSURL *)url forSaveOperation:(UIDocumentSaveOperation)saveOperation completionHandler:(void (^ __nullable)(BOOL success))completionHandler] to save a duplicate at a new URL, however the original file -[UIDocument fileURL] is deleted when doing so?
UIDocument saveToURL deletes original file
108 Views Asked by catlan AtThere are 3 best solutions below
On
Yeah, UIDocument does that. It's a pain.
If you want to save a UIDocument to a new URL without losing the original, you can do it with a function like this:
func saveDocument(_ document: UIDocument, asCopyTo url: URL, completion: @escaping (Bool, Error?) -> Void) {
let oldUrl = document.fileURL
document.save(to: url, for: .forOverwriting) { success in
if success {
do {
try FileManager.default.copyItem(at: url, to: oldUrl)
} catch let error {
completion(false, error)
}
}
completion(success, nil)
}
}
This first saves the existing document at its new url, updating the fileURL so that the UIDocument object remains current and doesn't keep on saving over the old file. Once that's done, and assuming it succeeds, we use FileManager to copy the new file's data back to its original location.
It might seem weird to delete and recreate the file at its original location, and I agree that UIDocument is a pretty horrible creation, but this does at least save you from having to reload the document from the file system to maintain the correct location.
On
If you have a UIDocument subclass, add this to the subclass and call it instead of the normal save(to:for:). If there's an existing document in the target location, this moves it to a tmp location, increments a number after the name until it finds an unused one, saves the document, and finally restores the original from the tmp location.
func saveRenamingExistingFileIfNecessary(completionHandler: @escaping (_ success: Bool)->()) {
let fm = FileManager.default
let targetDir = fileURL.deletingLastPathComponent()
var tmpURL: URL?
let originalURL = fileURL
var workingFilename = fileURL.lastPathComponent
var workingURL = fileURL
if fm.fileExists(atPath: fileURL.path) {
tmpURL = targetDir.appendingPathComponent("tmp.data")
do {
try? fm.removeItem(at: tmpURL!)
try fm.copyItem(at: fileURL, to: tmpURL!)
} catch {
print("*** save error: \(error)")
completionHandler(false)
return
}
let filenameWithoutExtension = ((fileURL.lastPathComponent as NSString).deletingPathExtension as String)
let fileExtension = fileURL.pathExtension
var fileCounter = 2
while fm.fileExists(atPath: workingURL.path) {
workingFilename = "\(filenameWithoutExtension) \(fileCounter).\(fileExtension)"
workingURL = targetDir.appendingPathComponent(workingFilename, isDirectory: false)
fileCounter += 1
}
}
save(to: workingURL, for: .forCreating) { success in
if success {
if let tmpURL {
do {
try fm.copyItem(at: tmpURL, to: originalURL)
try fm.removeItem(at: tmpURL)
} catch {
print("*** save error: \(error)")
completionHandler(false)
return
}
}
}
completionHandler(success)
}
}
I couldn't find any documentation regarding this, but it seems to be intend.
¯\_(ツ)_/¯Here is the Hopper disassembly of iOS 15.2: