Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIDocument not saving to file despite indicating success

I'm trying to open, modify, and save a file in iCloud Drive using UIDocument. When I call save(to:for:completionHandler:) with the file location and using .forOverwriting for the UIDocumentSaveOperation, it completes with a status of success = true. However, the iCloud file (as seen in both desktop and iOS file browser) does not update, and when reopening the file, the changes are not shown. I've verified that contents(forType:) returns the correct (modified) file contents when saving.

(Note: I've already looked at this question, but it wasn't very helpful 😕)

Here are the relevant sections of code:

MainViewController.swift:

var saveFile: SBDocument?

@IBAction func bbiOpen_pressed(_ sender: UIBarButtonItem) {

    if saveFile == nil {
        let importMenu = UIDocumentMenuViewController(documentTypes: self.UTIs, in: .import)
        importMenu.delegate = self
        importMenu.popoverPresentationController?.barButtonItem = bbiOpen
        self.present(importMenu, animated: true, completion: nil)
    } else {
        willClose()
    }

}

func willClose(_ action: UIAlertAction?) {
    if saveFile!.hasUnsavedChanges {
        dlgYesNoCancel(self, title: "Save Changes?", message: "Would you like to save the changes to your document before closing?", onYes: doSaveAndClose, onNo: doClose, onCancel: nil)
    } else {
        doSaveAndClose(action)
    }
}

func doSaveAndClose(_ action: UIAlertAction?) {
    saveFile?.save(to: saveFileURL!, for: .forOverwriting, completionHandler: { Void in
        self.saveFile?.close(completionHandler: self.didClose)
    })
}

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
    saveFile = SBDocument(fileURL: url)
    saveFile!.open(completionHandler: { success in self.finishOpen(didCompleteSuccessfully: success) })
}

func finishOpen(didCompleteSuccessfully result: Bool) {
    if result {
        print(saveFile!.localizedName)
        saveFileURL = saveFile!.fileURL
        saveFileName = saveFile!.localizedName
        self.navTitleBar.prompt = saveFileName
        bbiOpen.title = NSLocalizedString("titleClose", comment: "Close")
        bbiOpen.style = .plain
    } else {
        saveFile = nil
    }
}

@IBAction func bbiSave_pressed(_ sender: UIBarButtonItem) {
    self.saveFile!.save(to: self.saveFileURL!, for: .forOverwriting, completionHandler: self.didSave)
}

func didSave(_ success: Bool) {
    guard success else {
        print("Error saving soundboard file to \(String(describing: saveFileURL))")
        return
    }
    print("File saved successfully")        
}

SBDocument.swift:

class SBDocument: UIDocument {
    override var fileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
    override var savingFileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }

    override init(fileURL url: URL) {
        super.init(fileURL: url)
    }

    override func contents(forType typeName: String) throws -> Any {
        let arr = NSArray(array: SoundEffects)
        let data: NSData = NSKeyedArchiver.archivedData(withRootObject: arr) as NSData
        return data
    }
}

Update: I really need help with this, and I've tried everything I can think of to fix this. Any assistance you could give me would be greatly appreciated.

like image 426
Matt Avatar asked Apr 03 '17 19:04

Matt


2 Answers

The way the initial file generation works for me is:

    let doc = YourUIDocumentClass(fileURL: fileURL) 
    doc.save(to: fileURL, for: .forCreating) { success in
        ...            
    }

Then modify the file and then do:

    doc.save(to: fileURL, for: .forOverwriting)  { success in
        ...            
    }

when done. And subsequent accesses to the file are done by:

    doc.open() { success in
        ...
    }

    doc.close() { success in
        ...            
    }

You might also need to do a:

    doc.updateChangeCount(.done)

while the file is open to tell the document there are unsaved changes. Just setting this will cause a save after a few seconds. You don't even need the close to do that.

The ... means that you either have to nest all these or make sure there is enough time between them so they are completed.

like image 197
Sojourner9 Avatar answered Nov 07 '22 19:11

Sojourner9


In addition to the above answers, another cause of this can be that there's an error during the save process unrelated to contents(forType:).

For example, if you implement fileAttributesToWrite(to:for:) and throw an error, then this can cause a UIDocumentState.savingError even though contents(forType:) returns the correct data.

like image 45
Adrian Schönig Avatar answered Nov 07 '22 18:11

Adrian Schönig