Tracking upload progress of iCloud files - objective-c

I'm writing a Unity plugin which makes use of iCloud.
Now the way I currently use to move files to iCloud is using this code:
[byteDocument saveToURL:savePathURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
if (ubiquitousContainerURL != nil)
{
NSURL *ubiquityDocumentDirectory = [ubiquitousContainerURL
URLByAppendingPathComponent:#"Documents"
isDirectory:YES];
ubiquityDocumentDirectory = [ubiquityDocumentDirectory URLByAppendingPathComponent:[NSString stringWithFormat:#"%#%#", filename, extension]];
NSFileManager* fm = [NSFileManager defaultManager];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError* error = nil;
if (![fm fileExistsAtPath:ubiquityDocumentDirectory.path])
{
NSLog(#"Attemping to move to cloud");
didMoveToCloud = [fm setUbiquitous:YES itemAtURL:savePathURL destinationURL:ubiquityDocumentDirectory error:nil];
}
else
{
NSLog(#"Attemping to replace in cloud");
didMoveToCloud = [fm replaceItemAtURL:ubiquityDocumentDirectory withItemAtURL:savePathURL backupItemName:nil options:0 resultingItemURL:nil error:&error];
}
if (didMoveToCloud)
{
NSLog(#"Did move text document successfully to cloud");
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
else
{
NSLog(#"Failed to move text document to cloud");
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
if (error != nil)
{
NSLog(#"Error saving text document to cloud: %#", error.description);
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
});
}
}];
Basically I'm just using a simple inherited class of UIDocument called CloudByteDocument. I first save it locally on the device and then move it to iCloud afterwards.
Now the problem is that using SetUbiquituos does not seem to provide any way of tracking the upload progress? I'm already attached to both MSMetaDataQuery observerser, that is:
// Listen for the second phase live-updating
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(queryDidReceiveNotification:) name:NSMetadataQueryDidUpdateNotification object:nil];
// Listen for the first phase gathering
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(queryIsDoneGathering:) name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
And I also realize that you can get things like upload and download progress during the NSMetadataQueryDidUpdateNotification, however this update method doesn't seem to provide very reliable results all the time.
I was wondering if there is any way in which you can use an actual callback method to notify you when a file has been uploaded to iCloud? Or is the NSMetadataQueryDidUpdateNotification method the standard way of doing so?
Any hints and help would be appriciated :)
Thank you for your time :)

Related

How can I add media attachments to my push notifications in an iOS 10 app?

There are multiple examples how you should set up your project to add rich notifications which use 'media attachments' technology to show images. I've read most of them but something I missed, because my project does not display any rich notifications with this payload (tested with APNS-Tool and Boodle):
{
"aps":{
"alert":{
"title": "Rich test",
"body": "Dancing Banana"
},
"mutable-content": 1
}
}
The notification is visible, but on 3D Touch, there are no additional infos displayed (like the image or the modified title). The notification extension breakpoints and NSLog messages are also not working.
Here is my demo project: https://github.com/gklka/RichTest
What I've done:
Created a new project
implemented iOS-style authorization:
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(#"Granted");
[[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
NSLog(#"Error registering: %#", error);
}
}];
added debug to AppDelegate:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(#"Token: %#", deviceToken);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Error: %#", error);
}
Added a new target to the project: Notification Service Extension:
Added banana.gif to the project
Added code to add banana attachment into NotificationService.m
self.bestAttemptContent.title = [NSString stringWithFormat:#"%# [modified]", self.bestAttemptContent.title];
// Add image attachment
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"banana" withExtension:#"gif"];
NSError *error;
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:#"banana" URL:fileURL options:nil error:&error];
self.bestAttemptContent.attachments = #[attachment];
What did I miss?
Additional info: The same thing works when I use local notifications instead of remote notifications.
You're most of the way there. The way you're going to send the attachment is usually as a URL in your payload. However, if you wanted to hard-code the attachment, like your code does, I think it would work, but I think you missed one critical component. I think the service extension does not have access to the main bundle or its resources. If you added a resource to the service extension and tried to load that (using something like [NSBundle bundleForClass:[NotificationService class]]), I suspect it would work.
However, if you send the URL as part of the payload, then you're going to load the image from that URL and not the bundle anyway. In that case, I'm pretty sure you also have to use startAccessingSecurityScopedResource on NSURL (along with stopAccessingSecurityScopedResource).
Hope that helps!

Can create, but not open, a UIManagedDocument with iCloud support

I am creating a new UIManagedDocument with iCloud support as follows:
Alloc and init with local sandbox URL
Set persistent store options to support iCloud: ubiquitousContentNameKey and ubiquitousContentURL. The name I'm generating uniquely and the URL is pointing to my ubiquityContainer / CoreData.
Save locally to sandbox with UIManagedDocument's saveToURL method.
In completion handler, move to iCloud with FileManager's setUbiquitous method.
So far, this dance works. (Well, sort of). After I call setUbiquitous, I get an error that says it WASN'T successful, however the document moves to the cloud. When it's done, I have a new document in the cloud. This appears to be a bug, as I've been able to replicate it with others' code.
I'm actually generating this document in a "Documents View Controller," which lists all of the documents in the cloud. So when this new document's final completion handler is finished, it shows up in the table view thanks to an NSMetadataQuery. So far, pretty standard usage I think.
To edit a document, the user taps and goes to a "Single View Document View Controller."
In this view controller, I need to "reopen" the selected document so the user can edit it.
So I go through series of steps again:
Alloc / init a UIManagedDocument with a fileURL -- this time, the URL is from the cloud.
Set my persistent store options, same as step 2 above, with same settings.
Now, I ATTEMPT step 3, which is to open the document from disk, but it fails. The document is in a state of "Closed | SavingError" and the attempt to open fails.
Does anyone know why my document would create OK, move to the cloud OK, but then fail to open on an immediate subsequent attempt? (Really, an attempt within that launch of the app - see below). Specifically, what would make a UIManagedDocument instance be created but in a closed, non-openable state?
Interestingly enough, if I quit the app and launch again, I can tap and reload the document and edit it.
And very occasionally I can create, then open, and edit very briefly, say insert one managedobject, and then it goes into this close | saving error state.
ERROR INFO:
I've subclassed UIManagedDocument and overrode the -handleError: method to try and get more information, and here's what I get (along with some other debugging logs I put in):
2012-10-05 14:57:06.000 Foundations[23687:907] Single Document View Controller View Did Load. Document: fileURL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/ documentState: [Closed]
2012-10-05 14:57:06.052 MyApp[23687:907] Document state changed. Current state: 5 fileURL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/ documentState: [Closed | SavingError]
2012-10-05 14:57:06.057 Foundations[23687:5303] UIManagedDocument error: The store name: New Document 34 is already in use.
Store URL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/StoreContent.nosync/persistentStore
In Use Store URL: file://localhost/var/mobile/Applications/D423F5FF-4B8E-4C3E-B908-11824D70FD34/Documents/New%20Document%2034/StoreContent.nosync/persistentStore
2012-10-05 14:57:06.059 MyApp[23687:5303] {
NSLocalizedDescription = "The store name: New Document 34 is already in use.\n\tStore URL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/StoreContent.nosync/persistentStore\n\tIn Use Store URL: file://localhost/var/mobile/Applications/D423F5FF-4B8E-4C3E-B908-11824D70FD34/Documents/New%20Document%2034/StoreContent.nosync/persistentStore\n";
NSPersistentStoreUbiquitousContentNameKey = "New Document 34";
}
The error seems to think I'm it create a store that already exists on the subsequent opening. Am I now supposed to set those iCloud option on the persistent store on a second opening? I've tried that approach and it didn't work either.
I've studied the Stanford lectures on UIManagedDocument and don't see what I'm doing wrong.
Here's my method to create the doc and move to cloud:
- (void) testCreatingICloudDocWithName:(NSString*)name
{
NSURL* cloudURL = [self.docManager.iCloudURL URLByAppendingPathComponent:name isDirectory:YES];
NSURL* fileURL = [self.docManager.localURL URLByAppendingPathComponent:name];
self.aWriting = [[FNFoundationDocument alloc] initWithFileURL:fileURL];
[self setPersistentStoreOptionsInDocument:self.aWriting];
[self.aWriting saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success == YES) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//create file coordinator
//move document to icloud
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError* coorError = nil;
[fileCoordinator coordinateWritingItemAtURL:cloudURL options:NSFileCoordinatorWritingForReplacing error:&coorError byAccessor:^(NSURL *newURL) {
if (coorError) {
NSLog(#"Coordinating writer error: %#", coorError);
}
NSFileManager* fm = [NSFileManager defaultManager];
NSError* error = nil;
NSLog(#"Before set ubiq");
[fm setUbiquitous:YES itemAtURL:fileURL destinationURL:newURL error:&error];
if (!error) {
NSLog(#"Set ubiquitous successfully.");
}
else NSLog(#"Error saving to cloud. Error: %#", error);
NSLog(#"State of Doc after error saving to cloud: %#", self.aWriting);
}];
});
}
}];
}
Here's where I set options for iCloud on the persistentStore:
- (void)setPersistentStoreOptionsInDocument:(FNDocument *)theDocument
{
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:[theDocument.fileURL lastPathComponent] forKey:NSPersistentStoreUbiquitousContentNameKey];
NSURL* coreDataLogDirectory = [self.docManager.coreDataLogsURL URLByAppendingPathComponent:[theDocument.fileURL lastPathComponent]];
NSLog(#"Core data log dir: %#", coreDataLogDirectory);
[options setObject:coreDataLogDirectory forKey:NSPersistentStoreUbiquitousContentURLKey];
theDocument.persistentStoreOptions = options;
}
And here's where I try to reopen it:
- (void) prepareDocForUse
{
NSURL* fileURL = self.singleDocument.fileURL;
if (![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
NSLog(#"File doesn't exist");
}
else if (self.singleDocument.documentState == UIDocumentStateClosed) {
// exists on disk, but we need to open it
[self.singleDocument openWithCompletionHandler:^(BOOL success) {
if (!success) {
NSError* error;
[self.singleDocument handleError:error userInteractionPermitted:NO];
}
[self setupFetchedResultsController];
}];
} else if (self.singleDocument.documentState == UIDocumentStateNormal) {
// already open and ready to use
[self setupFetchedResultsController];
}
}
Have you recently been testing various version of iOS? Try changing the title of your saved document to something other than "New Document 34", I was experiencing this same issue and I believe it had to do with conflicting documents saved from different sdk compilations of the app using the same document url.
I target iOS7, I use a single UIManagedDocument as my app DB with the goal to better integrate CoreData and iCloud as suggested by Apple in its documentation. I had the same problem, i solved with the following code.
Since I wrote it, I moved the PSC options settings inside the lazy instantiation of the UIManagedDocument.
My original code created, closed and then reopened the document using the callback with success standard functions. I found it on a book by Erika Sadun. Everything seemed ok but I couldn't reopen the just created and then closed document because it was in "savings error" state. I lost a week on it, I couldn't understand what I was doing wrong because until the reopen everything was perfect.
The following code works perfectly on my iPhone5 and iPad3.
Nicola
-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{
//If the CoreData local file exists then open it and perform the query
if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
NSLog(#"The CoreData local file in the application sandbox already exists.");
if (self.managedDocument.documentState == UIDocumentStateNormal){
NSLog(#"The CoreData local file it's in Normal state. Fetching data.");
fetchingDataBlock();
}else if (self.managedDocument.documentState == UIDocumentStateClosed){
NSLog(#"The CoreData local file it's in Closed state. I am opening it.");
[self.managedDocument openWithCompletionHandler:^(BOOL success) {
if(success){
NSLog(#"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
fetchingDataBlock();
}else{
NSLog(#"ERROR: Can't open the CoreData local file. Can't fetch the data.");
NSLog(#"%#", self.managedDocument);
return;
}
}];
}else{
NSLog(#"ERROR: The CoreData local file has an unexpected documentState: %#", self.managedDocument);
}
}else{
NSLog(#"The CoreData local file in the application sandbox did not exist.");
NSLog(#"Setting the UIManagedDocument PSC options.");
[self setPersistentStoreOptionsInDocument:self.managedDocument];
//Create the Core Data local File
[self.managedDocument saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if(success){
NSLog(#"SUCCESS: The CoreData local file has been created. Fetching data.");
fetchingDataBlock();
}else{
NSLog(#"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
NSLog(#"%#", self.managedDocument);
return;
}
}];
}
}

Calling -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] never returns

I have a straightforward NSDocument-based Mac OS X app in which I am trying to implement iCloud Document storage. I'm building with the 10.7 SDK.
I have provisioned my app for iCloud document storage and have included the necessary entitlements (AFAICT). The app builds, runs, and creates the local ubiquity container Documents directory correctly (this took a while, but that all seems to be working). I am using the NSFileCoordinator API as Apple recommended. I'm fairly certain I am using the correct UbiquityIdentifier as recommended by Apple (it's redacted below tho).
I have followed Apple's iCloud Document storage demo instructions in this WWDC 2011 video closely:
Session 107 AutoSave and Versions in Lion
My code looks almost identical to the code from that demo.
However, when I call my action to move the current document to the cloud, I experience liveness problems when calling the -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] method. It never returns.
Here is the relevant code from my NSDocument subclass. It is almost identical to Apple's WWDC demo code. Since this is an action, this is called on the main thread (as Apple's demo code showed). The deadlock occurs toward the end when the -setUbiquitous:itemAtURL:destinationURL:error: method is called. I have tried moving to a background thread, but it still never returns.
It appears that a semaphore is blocking while waiting for a signal that never arrives.
When running this code in the debugger, my source and destination URLs look correct, so I'm fairly certain they are correctly calculated and I have confirmed the directories exist on disk.
Am I doing anything obviously wrong which would lead to -setUbiquitous never returning?
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSString *appID = [NSString stringWithFormat:#"XXXXXXX.%#.macosx", bundleID];
BOOL makeUbiquitous = 1 == [sender tag];
NSURL *destURL = nil;
NSFileManager *mgr = [NSFileManager defaultManager];
if (makeUbiquitous) {
// get path to local ubiquity container Documents dir
NSURL *dirURL = [[mgr URLForUbiquityContainerIdentifier:appID] URLByAppendingPathComponent:#"Documents"];
if (!dirURL) {
NSLog(#"cannot find URLForUbiquityContainerIdentifier %#", appID);
return;
}
// create it if necessary
[mgr createDirectoryAtURL:dirURL withIntermediateDirectories:NO attributes:nil error:nil];
// ensure it exists
BOOL exists, isDir;
exists = [mgr fileExistsAtPath:[dirURL relativePath] isDirectory:&isDir];
if (!(exists && isDir)) {
NSLog(#"can't create local icloud dir");
return;
}
// append this doc's filename
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// get path to local Documents folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
// append this doc's filename
destURL = [[dirs objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSFileCoordinator *fc = [[[NSFileCoordinator alloc] initWithFilePresenter:self] autorelease];
[fc coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForMoving writingItemAtURL:destURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *fileURL, NSURL *destURL) {
NSError *err = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
[fc itemAtURL:fileURL didMoveToURL:destURL];
} else {
NSWindow *win = ... // get my window
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
}
}];
}
I don't know if these are the source of your problems, but here are some things I'm seeing:
-[NSFileManager URLForUbiquityContainerIdentifier:] may take a while, so you shouldn't invoke it on the main thread. see the "Locating the Ubiquity Container" section of this blog post
Doing this on the global queue means you should probably use an allocated NSFileManager and not the +defaultManager.
The block passed to the byAccessor portion of the coordinated write is not guaranteed to be called on any particular thread, so you shouldn't be manipulating NSWindows or presenting modal dialogs or anything from within that block (unless you've dispatched it back to the main queue).
I think pretty much all of the iCloud methods on NSFileManager will block until things complete. It's possible that what you're seeing is the method blocking and never returning because things aren't configured properly. I'd double and triple check your settings, maybe try to simplify the reproduction case. If it still isn't working, try filing a bug or contacting DTS.
Just shared this on Twitter with you, but I believe when using NSDocument you don't need to do any of the NSFileCoordinator stuff - just make the document ubiquitous and save.
Hmm,
did you try not using a ubiquity container identifier in code (sorry - ripped out of a project so I've pseudo-coded some of this):
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *iCloudDocumentsURL = [[fm URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"];
NSURL *iCloudFileURL = [iCloudDocumentsURL URLByAppendingPathComponent:[doc.fileURL lastPathComponent]];
ok = [fm setUbiquitous:YES itemAtURL:doc.fileURL destinationURL:iCloudRecipeURL error:&err];
NSLog(#"doc moved to iCloud, result: %d (%#)",ok,doc.fileURL.fileURL);
And then in your entitlements file:
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>[devID].com.yourcompany.appname</string>
</array>
Other than that, your code looks almost identical to mine (which works - except I'm not using NSDocument but rolling it all myself).
If this is the first place in your code that you are accessing iCloud look in Console.app for a message like this:
taskgated: killed yourAppID [pid 13532] because its use of the com.apple.developer.ubiquity-container-identifiers entitlement is not allowed
Anytime you see this message delete your apps container ~/Library/Containers/<yourAppID>
There may also be other useful messages in Console.app that will help you solve this issue.
I have found that deleting the app container is the new Clean Project when working with iCloud.
Ok, So I was finally able to solve the problem using Dunk's advice. I'm pretty sure the issue I was having is as follows:
Sometime after the WWDC video I was using as a guide was made, Apple completed the ubiquity APIs and removed the need to use an NSFileCoordinator object while saving from within an NSDocument subclass.
So the key was to remove both the creation of the NSFileCoordinator and the call to -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
I also moved this work onto a background thread, although I'm fairly certain that was not absolutely required to fix the issue (although it was certainly a good idea).
I shall now submit my completed code to Google's web crawlers in hopes of assisting future intrepid Xcoders.
Here's my complete solution which works:
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) {
NSBeep();
return;
}
BOOL makeUbiquitous = 1 == [sender tag];
if (makeUbiquitous) {
[self displayMoveToCloudDialog];
} else {
[self displayMoveFromCloudDialog];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doMoveToOrFromCloud:makeUbiquitous];
});
}
- (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSURL *destURL = nil;
NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
if (makeUbiquitous) {
NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
if (!dirURL) return;
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// move to local Documentss folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSError *err = nil;
void (^completion)(void) = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
completion = ^{
[self hideMoveToFromCloudDialog];
};
} else {
completion = ^{
[self hideMoveToFromCloudDialog];
NSWindow *win = [[self canvasWindowController] window];
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
};
}
dispatch_async(dispatch_get_main_queue(), completion);
}

NSMetadataQuery doesn't find iCloud files

I used as described in the Apple Docs NSMetadataQuery to search my iCloud file. I have only one file and I know its name. My problem is that sometimes this file doesn't exist (I guess because it has not yet been downloaded) and NSMetadataQuery is unable to find it.
Ever tried to force download with NSFileManager startDownloadingUbiquitousItemAtURL:error: and it returns me an error. (Read EDIT)
My solution is that I created the file the first time, then I guess it exists and I open it with UIDocument. But It couldn't exist or it could be the first time the user opens the app. I can't be sure of these things. My first question is: if UIDocument opens the file, it means that it found the file somewhere. How could it use the file if it DOESN'T EXIST?
And then, second question: If I app which has to manage multiple files or files with unknown name. How can I find them if NSMetadataQuery doesn't work.
EDIT:
if startDownloadingUbiquitousItemAtURL should be used to start downloading a file, how can I know when the file finished downloading (perhaps with a notification)? But, a more important thing: How can I download the file if is always says (removed original names)?
Error Domain=NSPOSIXErrorDomain Code=2 "The operation couldn’t be completed.
No such file or directory" UserInfo=0x166cb0 {
NSDescription=Unable to get real path for Path
'/private/var/mobile/Library/Mobile Documents/teamid~com~team~app/Documents/file.extension'
}
I suggest the following load routine for iCloud files. It involves 4 steps:
First, testing if iCloud is accessible
then look for your files (either look for a specific file like you indicated or for all files with a certain extension like *.txt, or if you don't really know what file extension you are looking for, something like NSPredicate *pred = [NSPredicate predicateWithFormat:#"NOT %K.pathExtension = '.'", NSMetadataItemFSNameKey]; will return all files which have an extension, like jpg, txt, dat etc.)
then checking if the query is done, and
finally attempt to load the file. If the file doesn't exist, create it. If it does exist, load it.
Here is the code that exemplifies these four steps:
- (void)loadData:(NSMetadataQuery *)query {
// (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document
if ([query resultCount] == 1) {
// found the file in iCloud
NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url];
//_document = doc;
doc.delegate = self.viewController;
self.viewController.document = doc;
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"AppDelegate: existing document opened from iCloud");
} else {
NSLog(#"AppDelegate: existing document failed to open from iCloud");
}
}];
} else {
// Nothing in iCloud: create a container for file and give it URL
NSLog(#"AppDelegate: ocument not found in iCloud.");
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:#"text.txt"];
MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage];
//_document = doc;
doc.delegate = self.viewController;
self.viewController.document = doc;
[doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"AppDelegate: new document save to iCloud");
[doc openWithCompletionHandler:^(BOOL success) {
NSLog(#"AppDelegate: new document opened from iCloud");
}];
}];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notification {
// (3) if Query is finished, this will send the result (i.e. either it found our text.dat or it didn't) to the next function
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
[self loadData:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
_query = nil; // we're done with it
}
-(void)loadDocument {
// (2) iCloud query: Looks if there exists a file called text.txt in the cloud
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
//SCOPE
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
//PREDICATE
NSPredicate *pred = [NSPredicate predicateWithFormat: #"%K == %#", NSMetadataItemFSNameKey, #"text.txt"];
[query setPredicate:pred];
//FINISHED?
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"AppDelegate: app did finish launching");
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil] autorelease];
} else {
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil] autorelease];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// (1) iCloud: init
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"AppDelegate: iCloud access!");
[self loadDocument];
} else {
NSLog(#"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings");
}
return YES;
}
edo42, did you solve the issue? I had purchased a new 4S to go back to AT&T and I had backed up my first 4S and then restored from backup on new 4S and then this was happening for me.
When I do a [iCloudFileURL getResource:&fileState forKey:NSURLIsUbiquitousItemKey error:&error] I get the same error.
Error Domain=NSPOSIXErrorDomain Code=2 "The operation couldn’t be completed. No such file or directory" UserInfo=0x448500 {NSDescription=Unable to get real path for Path '/private/var/mobile/Library/Mobile Documents/~UbiquitousDir~/Documents/MyDocument.ext'
(Works fine on my iPad 2)
Then I would do the NSMetadataQuery but then it comes back with no results. (Works on my iPad 2 just fine to see the document on iCloud).
I checked to see that the ubiquitous file exists but it is not there as the error suggest.
I tried using [fileMgr evictUbiquitousItemAtURL:ubiquitousDocumentsDir &error] and it does succeed. But when I try the NSMetadataQuery it still comes back with no results.
Hope this helps to figure what is wrong in both cases.
UPDATE: Ok there is a real problem with iCloud for this device that I restored from a backup:
I have deleted my app from the iPhone device.
I deleted the backup files for my app from Manage Storage.
I have even delete the file for my app from iCloud (Documents & Data) under Managed storage.
The iCloud file is no longer there and I look on iPad 2 and it's gone.
I started my app on my iPad 2 and he successfully saves a new iCloud document.
The new iCloud file shows on my iPhone.
I build and run my app on my device but he cannot download the file with NSMetadaQuery query.
I had a similar problem, metadataQueryDidUpdate and metadataQueryDidFinishGathering would fire, but the NSMetadataQuery's results would be empty. There was a provisioning profile problem that Xcode didn't tell me about until I attempted to test it on my device, where it failed, saying that my bundle identifier was invalid (it wasn't).
What fixed it for me was going into Preferences and clicking the Download All button for the provisioning profiles, and then doing a Clean in Xcode. Rebuild, run, and all my files showed up. Check in the developer portal to make sure none of your profiles are marked as Invalid. None of mine were, so it's not required to trigger this, but it can happen.
Not exactly scientific, but this did work for me.

Changes saved from one NSManagedObjectContext doesn't reflect on main NSManagedObjectContext

I have a main NSManagedObjectContext that's created in the appDelegate.
Now, I'm using another NSManagedObjectContext for editing/adding new objects without affecting the main NSManagedObjectContext, until I save them.
When I save the second NSManagedObjectContext, the changes are not reflected in the main NSManagedObjectContext, yet if I open the .sqlite database from simulator, the changes have been saved correctly into the .sqlite database. It doesn't matter if I fetch the data again, or even if I create a third NSManagedObjectContext, I cannot see those changes from the second NSManagedObjectContext, despite the fact those changes do actually exist on disk at this point.
If I quit and re-open the app, all changes are there.
What can cause the main NSManagedObjectContext to not see the new changes present in the persistent store?
Before this approach, I was using a single NSManagedObjectContext and undoManager, but I wanted to change it to use two different NSManagedObjectContexts.
The second NSManagedObjectContext save:
NSError* error = nil;
if ([managedObjectContext hasChanges]) {
NSLog(#"This new object has changes");
}
if (![managedObjectContext save:&error]) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
If you haven't already done so, I suggest reading the Apple documentation on Core Data : Change Management.
You need to notify the first context of the changes that were saved through the second context. When saving a context, it posts a NSManagedObjectContextDidSaveNotification. Register for that notification. In the handler method, merge into the first context the changes saved through the second context. For example:
// second managed object context save
// register for the notification
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(handleDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:secondManagedObjectContext];
// rest of the code ommitted for clarity
if (![secondManagedObjectContext save:&error]) {
// ...
}
// unregister from notification
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:secondManagedObjectContext];
Notification handler:
- (void)handleDidSaveNotification:(NSNotification *)note {
[firstManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}