Best way to use iCloud Documents Storage
Thanks to the comment above and with further readings, I've find a way to solve my problem.
Here is how I decided to do it:
- iCloud will be activated by default (if possible)
- The user can use an UISwitch to disable/enable iCloud in the App
- When the user disable iCloud, all the iCloud files will be transferred locally
- When the user enable iCloud, all the local files will be transferred in the iCloud Ubiquity container
- No data merging
Like this data will not be lost.
I guess almost everyone will use iCloud and everything will be transparent and painless. Anyway the files I sync are pretty small so it should work fine (so far it does).
I have 5 simples methods:
- Method to check if iCloud is available
- Method to return the Document URL according to user choice (iCloud OR Local)
- Method to delete all files in a Directory (files used by the app)
- Method to move files from local dir to iCloud container
- Method to move fies from iCloud container to local dir
Here is my class that handle the issue
class CloudDataManager {static let sharedInstance = CloudDataManager() // Singletonstruct DocumentsDirectory { static let localDocumentsURL: NSURL? = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: .UserDomainMask).last! as NSURL static let iCloudDocumentsURL: NSURL? = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)?.URLByAppendingPathComponent("Documents")}// Return the Document directory (Cloud OR Local)// To do in a background threadfunc getDocumentDiretoryURL() -> NSURL { print(DocumentsDirectory.iCloudDocumentsURL) print(DocumentsDirectory.localDocumentsURL) if userDefault.boolForKey("useCloud") && isCloudEnabled() { return DocumentsDirectory.iCloudDocumentsURL! } else { return DocumentsDirectory.localDocumentsURL! }}// Return true if iCloud is enabledfunc isCloudEnabled() -> Bool { if DocumentsDirectory.iCloudDocumentsURL != nil { return true } else { return false }}// Delete All files at URLfunc deleteFilesInDirectory(url: NSURL?) { let fileManager = NSFileManager.defaultManager() let enumerator = fileManager.enumeratorAtPath(url!.path!) while let file = enumerator?.nextObject() as? String { do { try fileManager.removeItemAtURL(url!.URLByAppendingPathComponent(file)) print("Files deleted") } catch let error as NSError { print("Failed deleting files : \(error)") } }}// Move local files to iCloud// iCloud will be cleared before any operation// No data mergingfunc moveFileToCloud() { if isCloudEnabled() { deleteFilesInDirectory(DocumentsDirectory.iCloudDocumentsURL!) // Clear destination let fileManager = NSFileManager.defaultManager() let enumerator = fileManager.enumeratorAtPath(DocumentsDirectory.localDocumentsURL!.path!) while let file = enumerator?.nextObject() as? String { do { try fileManager.setUbiquitous(true, itemAtURL: DocumentsDirectory.localDocumentsURL!.URLByAppendingPathComponent(file), destinationURL: DocumentsDirectory.iCloudDocumentsURL!.URLByAppendingPathComponent(file)) print("Moved to iCloud") } catch let error as NSError { print("Failed to move file to Cloud : \(error)") } } }}// Move iCloud files to local directory// Local dir will be cleared// No data mergingfunc moveFileToLocal() { if isCloudEnabled() { deleteFilesInDirectory(DocumentsDirectory.localDocumentsURL!) let fileManager = NSFileManager.defaultManager() let enumerator = fileManager.enumeratorAtPath(DocumentsDirectory.iCloudDocumentsURL!.path!) while let file = enumerator?.nextObject() as? String { do { try fileManager.setUbiquitous(false, itemAtURL: DocumentsDirectory.iCloudDocumentsURL!.URLByAppendingPathComponent(file), destinationURL: DocumentsDirectory.localDocumentsURL!.URLByAppendingPathComponent(file)) print("Moved to local dir") } catch let error as NSError { print("Failed to move file to local dir : \(error)") } } }}}
for those who wants to use SWIFT 3:NOTE: Instead of moving the data I just do copy. But the destination path is cleared before copy data there..
class CloudDataManager { static let sharedInstance = CloudDataManager() // Singleton struct DocumentsDirectory { static let localDocumentsURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: .userDomainMask).last! static let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") } // Return the Document directory (Cloud OR Local) // To do in a background thread func getDocumentDiretoryURL() -> URL { if isCloudEnabled() { return DocumentsDirectory.iCloudDocumentsURL! } else { return DocumentsDirectory.localDocumentsURL } } // Return true if iCloud is enabled func isCloudEnabled() -> Bool { if DocumentsDirectory.iCloudDocumentsURL != nil { return true } else { return false } } // Delete All files at URL func deleteFilesInDirectory(url: URL?) { let fileManager = FileManager.default let enumerator = fileManager.enumerator(atPath: url!.path) while let file = enumerator?.nextObject() as? String { do { try fileManager.removeItem(at: url!.appendingPathComponent(file)) print("Files deleted") } catch let error as NSError { print("Failed deleting files : \(error)") } } } // Copy local files to iCloud // iCloud will be cleared before any operation // No data merging func copyFileToCloud() { if isCloudEnabled() { deleteFilesInDirectory(url: DocumentsDirectory.iCloudDocumentsURL!) // Clear all files in iCloud Doc Dir let fileManager = FileManager.default let enumerator = fileManager.enumerator(atPath: DocumentsDirectory.localDocumentsURL.path) while let file = enumerator?.nextObject() as? String { do { try fileManager.copyItem(at: DocumentsDirectory.localDocumentsURL.appendingPathComponent(file), to: DocumentsDirectory.iCloudDocumentsURL!.appendingPathComponent(file)) print("Copied to iCloud") } catch let error as NSError { print("Failed to move file to Cloud : \(error)") } } } } // Copy iCloud files to local directory // Local dir will be cleared // No data merging func copyFileToLocal() { if isCloudEnabled() { deleteFilesInDirectory(url: DocumentsDirectory.localDocumentsURL) let fileManager = FileManager.default let enumerator = fileManager.enumerator(atPath: DocumentsDirectory.iCloudDocumentsURL!.path) while let file = enumerator?.nextObject() as? String { do { try fileManager.copyItem(at: DocumentsDirectory.iCloudDocumentsURL!.appendingPathComponent(file), to: DocumentsDirectory.localDocumentsURL.appendingPathComponent(file)) print("Moved to local dir") } catch let error as NSError { print("Failed to move file to local dir : \(error)") } } } }}
Check this link: iCloud basics and code sample
If the information that you are storing are simple, it's better to use NSUserDefaults. You don't want to ask iCloud for basic information.