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")
}
})
}