Reputation: 659
I am currently using local storage in my iOS App. The user data is stored in the Document Directory and now I am planning to use iCloud Documents storage instead.
Here is how I intend to do it :
Checking if iCloud is available on the device
If yes, use URLForUbiquityContainerIdentifier to get the iCloud container URL
Save new files and documents to this new URL
For that I am using this code that will return the URL of the document folder (iCloud or local)
class CloudDataManager {
class func getDocumentDiretoryURL() -> NSURL {
let localDocumentsURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: .UserDomainMask).last! as NSURL
let iCloudDocumentsURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)?.URLByAppendingPathComponent("Documents")
if userDefault.boolForKey("useCloud") && iCloudDocumentsURL != nil {
return iCloudDocumentsURL!
} else {
return localDocumentsURL
}
}
}
Is it the best practice? I am worried problems will occur if one day iCloud isn't available so the local directory will be used instead of the cloud container and will be empty. Thank you.
Upvotes: 14
Views: 13729
Reputation: 3932
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)")
}
}
}
}
}
Upvotes: 8
Reputation: 659
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:
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:
Here is my class that handle the issue
class CloudDataManager {
static let sharedInstance = CloudDataManager() // Singleton
struct 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 thread
func 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 enabled
func isCloudEnabled() -> Bool {
if DocumentsDirectory.iCloudDocumentsURL != nil { return true }
else { return false }
}
// Delete All files at URL
func 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 merging
func 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 merging
func 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)")
}
}
}
}
}
Upvotes: 21
Reputation: 3593
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.
Upvotes: 2