I'm storing files in iCloud as a means of backup. Uploading and downloading files seems to work fine, but if I delete the app on a device and re-install the app on that same device, I no longer see the files that were uploaded to iCloud (even from that device just before deleting the app). The ubiquityIdentityToken is the same from all installs. I'm not trying to sync among devices, just store and retrieve. I can see the files in \settings\icloud\manage storage\ but not by running this code:
func createListOfSQLBaseFilesIniCloudDocumentsDirectory() -> [String]{
let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
var iCloudBackupSQLFiles = [String]()
do{
let directoryContents = try FileManager.default.contentsOfDirectory(at: iCloudDocumentsURL!, includingPropertiesForKeys: nil, options: [])
for myFile in directoryContents {
if myFile.pathExtension == "sqlite" {
let fileWithExtension = myFile.lastPathComponent
//backupSQLFiles is an array of full file names - including the extension
iCloudBackupSQLFiles.append(fileWithExtension)
}//if myFile
}//for in
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}//do catch
return iCloudBackupSQLFiles
}//createListOfSQLBaseFilesIniCloudDocumentsDirectory
Any guidance would be appreciated. Swift 3, iOS 10, Xcode 8
Hard to believe no one else has had this issue. Again, this is for simple file storage and retrieval, not syncing dynamically among devices.
The gist of the issue is that iCloud does not automatically sync cloud files down to a new device. Your code must do that. So if you remove an app from a device (but not from iCloud) and reinstall that same app, the app will not see prior iCloud files. You can add new ones and see them with the code above, but you are really just seeing the local ubiquitous container copy. To see previous items you need to perform a metadataQuery on iCloud, parse the filenames of the files of interest from the metadataQuery results and then run
startDownloadingUbiquitousItem(at:) on each file. For example, make an array of files in iCloud from the metadataQuery results and put this do-catch in a for-in loop.
let fileManager = FileManager.default
let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true)
let iCloudDocumentToCheckURL = iCloudDocumentsURL?.appendingPathComponent(whateverFileName, isDirectory: false)
do {
try fileManager.startDownloadingUbiquitousItem(at: iCloudDocumentToCheckURL!)
print("tested file: \(whateverFileName)")
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}//do catch
Related
I have a Core Data+CloudKit configuration where I need to provide the user with some initial data as a starting point. It's only a few records/objects so at first I tried hardcoding the data and writing to Core Data on first launch. Users are then able to add to and change the starting point data.
When I enabled CloudKit syncing, I discovered that when the second device on the same account was launched, the newly written default data for that device was 'newer' from CloudKit's point of view. That meant that the default data on the second device would sync and overwrite the user's own data on the first device. That's bad.
I believe the right solution is to provide pre-populated Core Data file (.sqlite) files in the app, so that on first launch the default data is already older than anything the user would have created on other devices. Most of the tutorials and sample code for doing this are very old, referencing Swift 3, Xcode 7, etc. I'm looking for a solution that works when using NSPersistentCloudKitContainer.
I tried the code in the answer to this SO question, changing the type for persistentContainer to NSPersistentCloudKitContainer. This compiles and runs, but the result is that my app launches with no pre-loaded data, and I'm not sure why. I have included the three .sqlite files in my Xcode project, and added them to the 'Copy Bundle Resources' list in 'Build Phases'.
Here is my code:
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let appName = Bundle.main.infoDictionary!["CFBundleName"] as! String
let container = NSPersistentCloudKitContainer(name: appName)
//Pre-load the default Core Data (Category names) on first launch
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!.appendingPathComponent(appName + ".sqlite")
let storeUrlFolder = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!
if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
let seededDataUrl2 = Bundle.main.url(forResource: appName, withExtension: "sqlite-shm")
let seededDataUrl3 = Bundle.main.url(forResource: appName, withExtension: "sqlite-wal")
try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
try! FileManager.default.copyItem(at: seededDataUrl2!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-shm"))
try! FileManager.default.copyItem(at: seededDataUrl3!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-wal"))
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
I believe you are not seeing any data because your code looks into the .documentDirectory, and PersistentCloudKitContainer looks for databases in the Application Support directory by default.
In AppDelegate.swift, on first launch, the intent is to place some sample docs in the local Documents folder, or in the iCloud Documents folder if iCloud is enabled.
var templates = NSBundle.mainBundle().pathsForResourcesOfType(AppDelegate.myExtension, inDirectory: "Templates")
dispatch_async(appDelegateQueue) {
self.ubiquityURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)
if self.ubiquityURL != nil && templates.count != 0 {
// Move sample documents from Templates to iCloud directory on initial launch
for template in templates {
let tempurl = NSURL(fileURLWithPath: template)
let title = tempurl.URLByDeletingPathExtension?.lastPathComponent
let ubiquitousDestinationURL = self.ubiquityURL?.URLByAppendingPathComponent(title!).URLByAppendingPathExtension(AppDelegate.myExtension)
// let exists = NSFileManager().isUbiquitousItemAtURL(ubiquitousDestinationURL!)
do {
try NSFileManager.defaultManager().setUbiquitous(true, itemAtURL: tempurl, destinationURL: ubiquitousDestinationURL!)
}
catch let error as NSError {
print("Failed to move file \(title!) to iCloud: \(error)")
}
}
}
return
}
Before running this, I delete the app from the device and make sure no doc of that name is in iCloud. On first launch, without iCloud, the sample docs copy properly into the local Documents folder. With iCloud, this code runs, and the setUbiquitous call results in an error that says the file already exists. The commented call to isUbiquitousItemAtURL also returns true.
What might be making these calls register that a file exists that I'm pretty sure doesn't? Thank you!
The file already exists, so just replace it
The primary solution...in all the trial and error, I'd forgotten to put "Documents" back in the url. Should be:
let ubiquitousDestinationURL = self.ubiquityURL?.URLByAppendingPathComponent("Documents").URLByAppendingPathComponent(title!).URLByAppendingPathExtension(AppDelegate.myExtension)
Without that, wrote the file to the wrong directory, and so I couldn't see it by normal means.
I want to upload Images, Videos and some PList files on iCloud on Button Click event. I did following code but there are some issues.
(Q.1) It takes too much time to upload data on iCloud Dashboard http://icloud.developer.apple.com/dashboard/.
(Q.2) Sometime Dashboard shows data and sometimes not.
(Q.3) One important question: right now its data shows in iCloud Dashboard, after app go live will it be visible to user's iCloud drive? because I need to this: data will visible to user's iCloud drive in a folder.
(Q.4) As I want to upload images, video and plist files, so my following coding approach is correct or not? else I need to use Key-Value storage or iCloud-Document upload or iCloud Drive upload? (right now i am using iCloud Drive upload method)
Below is my code:
private let pathsForDocDir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let DOCUMENT_DIRECTORY: AnyObject = pathsForDocDir[0]
let filePath = DOCUMENT_DIRECTORY.stringByAppendingPathComponent("image1.png")
let File : CKAsset? = CKAsset(fileURL: NSURL(fileURLWithPath: filePath))
let newRecord:CKRecord = CKRecord(recordType: "album")
newRecord.setValue(File, forKey: "ImageOrVideo")
let privateDB = CKContainer.defaultContainer().privateCloudDatabase
privateDB.saveRecord(newRecord) { (record : CKRecord?, error : NSError?) in
if error != nil {
print(error?.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
print("finished")
}
}
}
I am using this code in For loop because I have multiple files to upload.
(Q.5) And also I need to click on "Add Record ID Query Index" on iCloud Dashboard. you can see the screenshot of iCloud Dashboard.
I used SwiftData to create sqlite database in swift ,data base worked on simulator but when i want run app on real device it's not work and error is there is not such table, how i can solve this problem?
let fileMan = NSFileManager.defaultManager()
if (fileMan.fileExistsAtPath(dbPath)){
if let source = NSBundle.mainBundle().resourcePath?.stringByAppendingPathComponent(databaseStr){
if (fileMan.fileExistsAtPath(source)){
print("SQLiteDB - file \(databaseStr) not found in bundle")
} else {
// var error: NSError?
do {
try fileMan.copyItemAtPath(dbPath, toPath: source )
} catch _ {
}
}
}
}
return dbPath
}
Delete the App from the device and clean your project helps in many cases.
If it still don't work try to open the sqlite database. firefox sqlite-manager
But i think the easiest solution would be to delete the database and create a new one. After the delete you also need to remove the app from your device and simulator.
I am saving images captured from the camera within the local sandbox (filesystem) and store the filepath within my app to show the images (using Swift). I see that if I hit play in XCode, the images will be removed (which is ok)
Now I wonder what would happen if I submit this to the app store, the user saves images and I will update the app later on. Will the images will be removed as well?
To store the image, I use this function..
func saveImageLocally(imageData:NSData!) -> String{
let time = NSDate().timeIntervalSince1970
let fileManager = NSFileManager.defaultManager()
let dir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0].stringByAppendingPathComponent(subDirForImage) as String
if !fileManager.fileExistsAtPath(dir) {
var error: NSError?
if !fileManager.createDirectoryAtPath(dir, withIntermediateDirectories: true, attributes: nil, error: &error) {
println("Unable to create directory: \(error)")
return ""
}
}
let path = dir.stringByAppendingPathComponent("IMAGENAME\(Int(time)).png")
var error: NSError?
if !imageData.writeToFile(path, options: NSDataWritingOptions.DataWritingAtomic, error: &error) {
println("error writing file: \(error)")
return ""
}
return path
}
Anything you store in your documents folder will persist through app updates providing you keep the same app ID and increment the version number.
Though it is worth noting that the full path to the app sandbox will be different (there will be a new sandbox for the update and the old data copied into it), so make sure you are only accessing resources by storing relative paths etc and are not storing full paths to images and expecting them to resolve after an update.