Loading and Saving of a document

A few consideration about UIDocument:

Notice 1:

This Class is used for supporting ducument based app ,In the pattern MVC it's a Model class for your data, UIDocument is an abstract class so you have to subclass it in your app.

Generally two methods of UIDocument are to be overriden in your subclass ,"contentsForType" for saving operation and "loadFromContents" for writing operation, the important points regarding these two methods is that they are executed

in the thread that call them , for writing operation contentsForType will be executed on the same Queue that saveToURL is called and for reading operation "loadFromContents" is executed in the same Queue that  openWithCompletionHandle is called

but real physical file writing/reading on HardDisk, actualley are done in another thread so then asynchronous.


Notice 2:

To support autoSave feature in your app , you generally use NSUndoManger property from within the property undoManager of UIDocument or by calling  updateChangeCount.



[You can also refister for an undo to perform some action inside UIDocument like this


class myUIDocument : UIDocument{


setUpUndoListener(){

self.undoManager.registerUndoWithTarget(self, selector: Selector("onUndo"), object: nil)

self.undoManager.undo()

}

onUndo(){

}

}

]


Notice 3: type of intermediate data presented for Serializing/Deserializing [or marshaling/unmarshaling] are either NSData or NSFileWraper

*********************************************************************

NSFilePresenter,NSFileCoordinatore :

When using UIDocument in some scenario the document file you're working on, can be accessed by other objects within your process

to coordinate file access and notify each objects the changes made by each other  we are using NSFileCoordinatore and NSFilePresenter in conjunction , NSFileCoordinatore  is the manager for actual file operation such as opening/closing/deleting/reading/writing.........and NSFilePresenter is a protocol to notify changes to any objects conform to it

so in oredr to be able to get notified the object should be conform to NSFilePresenter 

NSFileCoordinatore internally has a Queue to push thoses objects, to do that you call addFilePresenter(obj) method from NSFileCoordinatore 


prototype for coordinateWritingItemAtURL in SDK :


func coordinateWritingItemAtURL(_ url: NSURL,

                        options options: NSFileCoordinatorWritingOptions,

                          error outError: NSErrorPointer,

                     byAccessor writer: (NSURL!) -> Void)



Notice : In general UIDocument handles files operation on async way , if you like perform any extra operation which is not handled by UIDocument by default you should similarly do it in an async way ,

the following example shows a Delete operation


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

var fileCoordinator = NSFileCoordinator(filePresenter: nil)

fileCoordinator.coordinateWritingItemAtURL(aURL,

options: NSFileCoordinatorWritingOptions.ForDeleting,

error: nil,

byAccessor: {

writingURL in

var fileManager = NSFileManager()

fileManager.removeItemAtURL(writingURL, error: nil)

})

})


As you see we are not interesting to change notification since we passed nil as NSFilePresenter, by the way you still are able to

add an observer to achieves some changes as below


NSNotificationCenter.defaultCenter().addObserver(anObserver,

selector: "documentStateChanged:",

name: UIDocumentStateChangedNotification,

object: doc)





********sample example code snippet***************

### File Coordinator & Presenter


class FileManager:NSObject,NSFilePresenter { //You need to inherit for NSObject

    var fileUrl:NSURL?

    let operationQueue=NSOperationQueue()

    var contentToWrite=String() //This holds content

    var deleted=false //indicate if the file has been deleted

    var presentedItemURL: NSURL? {

        return fileUrl

    }

    var presentedItemOperationQueue: NSOperationQueue {

        return operationQueue

    }

    init(filePath: String) {

        fileUrl=NSURL(fileURLWithPath: filePath)

        super.init()

        NSFileCoordinator.addFilePresenter(self)

    }

    init(fileUrl:NSURL) {

        self.fileUrl=fileUrl

        super.init()

        NSFileCoordinator.addFilePresenter(self)

    }

    func write(content:String){

        contentToWrite+=content

    }

    func writeToFile(data: NSData)->Bool{

        if deleted{

            return false

        }

        let fileCordinator=NSFileCoordinator(filePresenter: nil)

        var error:NSError?

        var result=false

        fileCordinator.coordinateReadingItemAtURL(fileUrl!, options: NSFileCoordinatorReadingOptions.allZeros, writingItemAtURL: fileUrl!, options: NSFileCoordinatorWritingOptions.ForMerging, error: &error){

            (readUrl:NSURL!,writeUrl:NSURL!) -> Void in

            let fileHandle=NSFileHandle(forWritingToURL: writeUrl, error: &error)

            if error == nil{

                fileHandle?.seekToEndOfFile()

                fileHandle?.writeData(data)

                result=true

            }

        }

        return result

    }

    func writeToFile(content: String)->Bool{

        let data=content.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        return writeToFile(data!)

    }

    func readFromFile()->NSData?{

        var data:NSData?

        if fileUrl == nil || deleted{

            return data

        }

        var error:NSError?

        let fileCordinator=NSFileCoordinator(filePresenter: nil)

        fileCordinator.coordinateReadingItemAtURL(fileUrl!, options: NSFileCoordinatorReadingOptions.allZeros, error:&error){

            (readUrl:NSURL!) -> Void in

            data=NSData(contentsOfURL: readUrl)

        }

        return data

    }

    func readString()->String?{

        let data=readFromFile()

        if data != nil{

            return NSString(data: data!, encoding: NSUTF8StringEncoding)

        }

        return nil

    }

    func fileDeleted()->Bool{

        return deleted

    }

    func syncPrint(content:String){ // I had to use synchronized print because I notice println() wasnt synchronized

        objc_sync_enter(self)

        println(content)

        objc_sync_exit(self)

    }

    

    

    //Conform to NSFilePresenter

    

    

    func relinquishPresentedItemToReader(reader: ((() -> Void)!) -> Void) {

        if countElements(contentToWrite) > 0 && writeToFile(contentToWrite){

            contentToWrite=""

        }

        reader({

           //do something after reading is done

        })

        syncPrint("Reliquish file for reading")

    }

    func relinquishPresentedItemToWriter(writer: ((() -> Void)!) -> Void) {

        if countElements(contentToWrite) > 0 && writeToFile(contentToWrite){

            contentToWrite=""

        }

        writer({

             //do something after writer is done

        })

        syncPrint("Reliquish file for writing")

     }

    func savePresentedItemChangesWithCompletionHandler(completionHandler: (NSError!) -> Void) {

        if countElements(contentToWrite) > 0 && writeToFile(contentToWrite){

            contentToWrite=""

        }

        completionHandler(nil)

        syncPrint("Saved file")

    }

    func accommodatePresentedItemDeletionWithCompletionHandler(completionHandler: (NSError!) -> Void) {

        deleted=true

    }

    func presentedItemDidMoveToURL(newURL: NSURL) {

        fileUrl=newURL

    }

    func presentedItemDidGainVersion(version: NSFileVersion) {

        

    }

    func presentedItemDidLoseVersion(version: NSFileVersion) {

        

    }

    func presentedItemDidResolveConflictVersion(version: NSFileVersion) {

        

    }

    func presentedSubitemAtURL(oldURL: NSURL, didMoveToURL newURL: NSURL) {

        

    }

    func presentedSubitemAtURL(url: NSURL, didGainVersion version: NSFileVersion) {

        

    }

    func presentedSubitemAtURL(url: NSURL, didLoseVersion version: NSFileVersion) {

        

    }

    func presentedSubitemAtURL(url: NSURL, didResolveConflictVersion version: NSFileVersion) {

        

    }

    func accommodatePresentedSubitemDeletionAtURL(url: NSURL, completionHandler: (NSError!) -> Void) {

        completionHandler(nil)

    }

    func presentedSubitemDidChangeAtURL(url: NSURL) {

        

    }

    func presentedSubitemDidAppearAtURL(url: NSURL) {

        

    }

    deinit{

        writeToFile(contentToWrite) //Before the object is deallocated, write all to file

        NSFileCoordinator.removeFilePresenter(self)

    }

    //Comform to NSFilePresenter


}

 



 Implementation


class ViewController: UIViewController {


    @IBOutlet weak var textView: UITextView!


    var document: MyDocument?

    var documentURL: NSURL?

.

.

.

}

let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, 

.UserDomainMask, true)


let docsDir = dirPaths[0] as! String

let dataFile = docsDir.stringByAppendingPathComponent("savefile.txt")


documentURL = NSURL(fileURLWithPath: dataFile)

document = MyDocument(fileURL: documentURL!)

document!.userText = ""

******************************************

if filemgr.fileExistsAtPath(dataFile) {


    document?.openWithCompletionHandler({(success: Bool) -> Void in

        if success {

            println("File open OK")

            self.textView.text = self.document?.userText

        } else {

            println("Failed to open file")   

        }

      })

    } else {

       document?.saveToURL(documentURL!, 

forSaveOperation: .ForCreating, 

completionHandler: {(success: Bool) -> Void in

         if success {

            println("File created OK")

         } else {

            println("Failed to create file")

         }

    })

}



override func loadFromContents(contents: AnyObject,

   ofType typeName: String, error outError: NSErrorPointer) -> Bool {


    if let userContent = contents as? NSData {

        userText = NSString(bytes: contents.bytes,

                   length: userContent.length,

                   encoding: NSUTF8StringEncoding) as? String

    }

    return true


override func contentsForType(typeName: String,

           error outError: NSErrorPointer) -> AnyObject {


    if let content = userText {


        var length =

              content.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)

        return NSData(bytes:content, length: length)


    } else {

        return NSData()

    }



============so to load a document in startup=================

override func viewDidLoad() {

    super.viewDidLoad()


    let dirPaths = 

          NSSearchPathForDirectoriesInDomains(.DocumentDirectory,

                            .UserDomainMask, true)


    let docsDir = dirPaths[0] as! String


    let dataFile = 

             docsDir.stringByAppendingPathComponent("savefile.txt")

    

    documentURL = NSURL(fileURLWithPath: dataFile)

    document = MyDocument(fileURL: documentURL!)

    document!.userText = ""


    let filemgr = NSFileManager.defaultManager()


    if filemgr.fileExistsAtPath(dataFile) {


        document?.openWithCompletionHandler({(success: Bool) -> Void in

            if success {

                println("File open OK")

                self.textView.text = self.document?.userText

            } else {

                println("Failed to open file")               }

        })

    } else {

        document?.saveToURL(documentURL!, forSaveOperation: 

.ForCreating,

                 completionHandler: {(success: Bool) -> Void in

            if success {

                println("File created OK")

            } else {

                println("Failed to create file ")

            }

        })

    }


========================saving document====================

@IBAction func saveDocument(sender: AnyObject) {

    document!.userText = textView.text


    document?.saveToURL(documentURL!, 

  forSaveOperation: .ForOverwriting, 

           completionHandler: {(success: Bool) -> Void in

        if success {

            println("File overwrite OK")

        } else {

            println("File overwrite failed")

        }

    })

}  

 © Xosrov 2016