I am revisiting a problem I presented here last year. I didn't ask the question correctly and believe I didn't provide the relevant code. I deleted that question and have rephrased it better (and longishI this time. This time I hope someone can understand my question. Maybe I have been looking too long at the screen to see my errors or iOS 10.2 has implemented new iCloud permissions. I have universal iOS (and macOS version) app that writes and reads text files to iCloud. Writing out the files to iCloud is not a problem. It is reading them back that has got me running around it circles.
(1) If an iPad writes out the file, it can read it back into the app, but it cannot read files written out by an iPhone using the same app. (2) If an iPhone writes out the file, it can read it back into the app, but it cannot read files written out by an iPad using the same app. (3) The Mac can read files written out by iOS devices but iOS devices cannot read the files written out by a macOS device.
Now when an attempt is made to read the file, it fails with error code 260 - That file doesn't exist. This happens for each of the aforementioned steps above. Since it's the same universal app, it has left me completely stupefied. The devices don't add anything specific to the device to the file name. This means I have misunderstood something about the caching of iCloud files on the device. I understood that the iOS (and macOS) take of this automatically.
Here is the code from my iOS project.
This is how I set up the metaDataQuery to get the file URL from iCloud (in iOS project):
//Get list of iCloud files or read a file from iCloud
func iCloud_ListOrReadFiles(_ accountName:String)
{
//Format for search predicate of document(s) in iCloud storage
var theFormat :String
//List documents or open a document
if(listMode)
{
requestedAccountName = kSuffix //List all files with suffix kSuffix (= "txt")
theFormat = "%K ENDSWITH %@" //Just like all text documents
} else {
requestedAccountName = accountName //Read the file
theFormat = "%K LIKE %@"
}
//And now set up the metaDataQuery metadataQuery = NSMetadataQuery() metadataQuery!.predicate = NSPredicate.init(format:theFormat, NSMetadataItemFSNameKey,requestedAccountName!)
metadataQuery!.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
NotificationCenter.default.addObserver(self,selector:#selector(metadataQueryDidFinishGathering),
name:NSNotification.Name.NSMetadataQueryDidFinishGathering,object:metadataQuery)
metadataQuery!.start()
}
This is how I process the file URLs returned from iCloud via the metaDataQuery (in iOS project):
func metadataQueryDidFinishGathering(_ notification:Notification) { let query = notification.object! as! NSMetadataQuery
query.disableUpdates() //Disable the querying updates
NotificationCenter.default.removeObserver(self, name:NSNotification.Name.NSMetadataQueryDidFinishGathering, object:query) //And remove from Notifications
query.stop() //Final nail in the coffin for this query
let results = NSArray.init(array: query.results)
let theCount = query.resultCount
//Vamoose if nothing found
if (theCount < 1) {
return
}
if(listMode) //Just create a list of iCloud files found
{
listMode = false
for i in 0..<theCount
{
let account = Accounts()
account.startDate = nil
account.stopDate = nil
account.modDate = nil //Can't set it below because the compiler is chocking up there.
account.location = 2
let urlString = ((results[i] as AnyObject).value(forAttribute: NSMetadataItemURLKey) as! URL).lastPathComponent
account.accountName = String( (urlString as NSString).deletingPathExtension)
listOfAccounts?.add(account)
}
//If user wants the list sorted alphabetiucally, then do it
if(appSettings.bSortingsFlag)
{
if( (((listOfAccounts?.count)!-1)) > onDeviceIndex) { //Sort only iCloud accounts
self.bubbleSortAccountNames(onDeviceIndex, toIndex:((listOfAccounts?.count)!-1))
}
}
} else { //Came here to read one text file
ubiquityURL = ((results[0] as AnyObject).value(forAttribute: NSMetadataItemURLKey) as? URL)! //URL of file
print(String(format:"metadataQueryDidFinishGathering:ubiquityURL = %@", ubiquityURL! as CVarArg)) //Let's see it
copyFromiCloud2Device(ubiquityURL! as NSURL) //Copy the file from iCloud (in the function below)
}
This is how I read the file from iCloud, using the iCloud URL returned by metaDataQuery. Below the code are the console prints (in iOS project):
/* Copy the text file from iCloud using standard NSFilemanager method copyItemAtURL No UIDocument class used here */ func copyFromiCloud2Device(_ iCloudURL : NSURL) { let nameWithSuffix = iCloudURL.lastPathComponent! //Extract just the file name (and suffix to use for target) let deviceURL = CPLib().fullURLPath(nameWithSuffix, inFolder: nil) //My function to get full path to the Documents folder on device
print("copyToDeviceDocumentsFolder:iCloudURL \(iCloudURL)")
print("copyToDeviceDocumentsFolder:deviceURL \(deviceURL)")
do {
try FileManager.default.copyItem(at: iCloudURL as URL, to:deviceURL) //Now copy the file from iCloud
//Process the contents after 0.25 seconds
Timer.scheduledTimer(timeInterval: 0.25, target:self, selector:#selector(converText2CoreData), userInfo:nil,repeats:false)
} catch let error as NSError { // End up here with error (code 260 = The file doesn't exist)
print("copyToDeviceDocumentsFolder:nameWithSuffix = \(nameWithSuffix)")
let noSuffix = String((nameWithSuffix as NSString).deletingPathExtension) //Remove the text suffix because user doesn't need to know that
let title = String(format:"Copy '%@' from iCloud",noSuffix!)
let errorDescription = String(format:"Error (%d), %@",error.code, error.localizedFailureReason!)
CPLib().showAlert(title, message:errorDescription, button:["Done"], calledBy:self, action:nil)
}
}
These are the print statements in: "metadataQueryDidFinishGathering" and "CopyFromiCloud2Device" (in iOS project):
metadataQueryDidFinishGathering:ubiquityURL = file:///private/var/mobile/Library/Mobile%20Documents/UZMZA52SXK~com~macsoftware~CheckPad/Documents/DemAccount.txt
copyToDeviceDocumentsFolder:iCloudURL file:///private/var/mobile/Library/Mobile%20Documents/UZMZA52SXK~com~macsoftware~CheckPad/Documents/DemAccount.txt copyToDeviceDocumentsFolder:deviceURL file:///var/mobile/Containers/Data/Application/DF9EE5C0-E3EA-444A-839D-C2E8C1D1B408/Documents/DemAccount.txt copyToDeviceDocumentsFolder:Failed to read nameWithSuffix = DemAccount.txt
+++++++++++++ This is the Objective C code used in macOS to read the same text files from iCloud (works):
/* Copy the file from iCloud using standard NSFilemanager method copyItemAtURL and NOT setUbiquitous. No UIDocument implements class used here */ -(void)copyFromiCloud:(NSString *)fileName { NSString *nameWithExtension = [fileName stringByAppendingPathExtension:kTEXTOne]; NSURL *deviceURL = [[CoreDataStuff accountsLocation:nil] URLByAppendingPathComponent:nameWithExtension]; NSURL *iCloudURL = [ubiquityContainerURL URLByAppendingPathComponent:nameWithExtension];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *error = nil;
//Copy the file from iCloud to local directory "Documents" on device
BOOL success = [fileManager copyItemAtURL:iCloudURL toURL:deviceURL error:&error];
if (!success)
[self showAnAlert:[NSString stringWithFormat:@"Copy %@ from iCloud",fileName] //Private library call
message:[NSString stringWithFormat:@"Aborting...%@",[error localizedFailureReason]] altButton:nil];
else {
[NSTimer scheduledTimerWithTimeInterval:0.25 //Set up a timer to fire up after .25 seconds
target:self
selector:@selector(convertText2CoreData:) //My function to convert the data to CoreData
userInfo:nil
repeats:NO];
}
}
I also noticed that when an iOS device fails to to find the file, this appears in the Xcode console:
**** Running on an iPad or iPhone ****
2017-03-25 20:09:15.543784 CheckPad[405:66745] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles 2017-03-25 20:09:15.554561 CheckPad[405:66745] [MC] Reading from public effective user settings.