0

My app uses a single document (UIDocument) to contain its information and never closes it while the app is open. I have implemented iCloud connectivity to save the file so I can share it across devices. When using the simulator (iOS7), I can open the document and save to it. If I quit the App in the simulator, I can still open the iCloud document and work with it when I relaunch.

I'm testing on an iPad (iOS7) and the simulator to work out the updates between devices. I can successfully open the iCloud document (saved by the simulator) and see everything properly on the iPad. However, if I quit the App on the iPad and relaunch it, I get success = NO and Document State = UIDocumentStateSavingError in my openWithCompletionHandler. I captured the error in the handleError method and found this:

Error 
Domain=NSCocoaErrorDomain 
Code=257 "The operation couldn’t be completed. (Cocoa error 257.)" 
UserInfo=0x14650360 {NSFilePath=/private/var/mobile/Library/Mobile Documents/2A9S2V8BH4~com~mishnookasoftware~selists/Lists.archive, NSUnderlyingError=0x1462b590 "The operation couldn’t be completed. Operation not permitted"}

This appears to be a permission denied error. I double checked the certificate to ensure iCloud was enabled for the App, and it is. I have tested to see if changes made on my iPad are pushed to iCloud, by quitting the App in the simulator and relaunching it to see the updates.

I do close the iCloud document in the App Delegate's applicationWillTerminate method ([iCloudListDocument closeWithCompletionHandler:nil];). I'm wondering if the file is not getting properly closed and thus cannot be reopened upon relaunch.

I have searched StackOverflow and could not find anything to help my situation. Any help is greatly appreciated.

Greg Walters
  • 181
  • 5
  • What happens if you run the iPad first before any document exists in iCloud ? – Duncan Groenewald Feb 11 '14 at 05:48
  • Unfortunately, I don't know how to delete the file in iCloud to test that. Also, the app creates the document and starts saving changes to it in iCloud once it starts using iCloud. I'm pretty sure I'd get the same error if I quit the app and relaunch on the iPad. – Greg Walters Feb 11 '14 at 14:24
  • Go to the ~Library/Mobile Documents/ directory on your Mac and look for the apps containerId and delete all the contents – Duncan Groenewald Feb 12 '14 at 04:33
  • Nope, same problem. When I store locally, the document is read and I can see the data after relaunch. When I store in iCloud, I get the Permission Denied error and the document is not read. – Greg Walters Feb 13 '14 at 03:25
  • Add some logging in your close completion handler to see if it is being successfully closed. – Duncan Groenewald Feb 13 '14 at 03:34
  • I never see the completion handler getting called when attempting a close in applicationWillResignActive. Apple docs say there is only 5 seconds before the app goes dormant after that method. Also, I only want to close the document when the app is going to terminate, which I don't seem to be able to determine at that point. There is an application flag (shouldExitAfterSendSuspend) but I can't see how to access it. – Greg Walters Feb 13 '14 at 05:17
  • Checking when the app relaunches, the status of the iCloud document is "closed" after the initWithFileURL. So, it appears that the document closes upon termination, but I don't have permission to open it again on relaunch. – Greg Walters Feb 13 '14 at 07:22
  • Try closing it in applicationDidEnterBackground and then open it in applicationDidEnterForeground to see if it makes any difference. – Duncan Groenewald Feb 13 '14 at 10:41
  • I am now closing the iCloud document whenever the app goes into the background (applicationDidEnterBackground) and opening it again when it comes into the foreground (applicationWillEnterForeground). This works fine, verified via debugging and NSLog entries, as long as I do not terminate the app. Once the app terminates, I cannot open the iCloud document again. – Greg Walters Feb 20 '14 at 20:23
  • When the app is using the iCloud document, I see the following error whenever the app terminates: Feb 20 13:09:43 Gregs-iPad com.apple.debugserver-300.2[18412] : 1 +0.000000 sec [47ec/1207]: error: ::read ( -1, 0x3d89ec, 18446744069414585344 ) => -1 err = Bad file descriptor (0x00000009) This happens whether I close the iCloud document or not. – Greg Walters Feb 20 '14 at 20:26
  • Remove all your code from willTerminate then run the app and switch the app to background (to close the file with no errors) and then kill the app and start it up again to see if it is now able to open the file properly. Are you also removing any observers you set up? – Duncan Groenewald Feb 21 '14 at 00:56
  • I don't have any code in willTerminate anymore, haven't for most of the testing. Ah, I did not remove the observers. Let me try that. – Greg Walters Feb 21 '14 at 16:05
  • Have you considered abandoning UIManagedDocument for the native stack? I have posted a couple of sample apps where most of plumbing has been done (for managing transitions to and from iCloud and making/restoring backups etc.) - http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/ – Duncan Groenewald Feb 21 '14 at 20:22
  • Duncan, No I had not, as I'm not using UIManagedDocument. I'm trying to migrate from an archive file using NSFileManager to using UIDocument. – Greg Walters Feb 24 '14 at 23:45
  • I have now checked to see if the iCloud document is in the sandbox before trying to open it. The document is /private/var/mobile/Library/Mobile Documents/2A9S2V8BH4~com~mishnookasoftware~selists/Documents/Lists.archive and fileExistsAtPath returns NO. I did connect to the iCloud document before the terminate, but I'm still not able to connect to it after relaunch. – Greg Walters Feb 24 '14 at 23:48
  • Right, sorry, ignore those last two posts they only apply to Core Data. So how are you moving the document to/from iCloud ? If you look at the sample apps at the link above I use iCloud to store and retrieve the Core Data backup files. If you are getting an iCloud document you must use a metadata query and then initiate a download if it has not been downloaded. If you hunt around in the sample app code you will find a method for copying files to the iCloud container and for querying the metadata to determine the download status. – Duncan Groenewald Feb 25 '14 at 00:19

1 Answers1

3

Okay, this was strange. I was saving the full path URL to the iCloud document in userDefaults and then reading it and trying to use it to open the iCloud document if I had a valid ubiquityIdentityToken upon relaunch. The full path contained the original URLForUbiquityContainerIdentifier with the document's final location and name appended to it. This action resulted in the document not being found ([[NSFileManager defaultManager] fileExistsAtPath:fileName] = NO, where fileName was taken from the saved full path URL).

Now, the weird part, for me anyway. I had to retrieve the URLForUbiquityContainerIdentifier (NSURL *myUbiquityContainer = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier: nil];) again, before I could access the full path URL I had saved. If I did this before I checked if the document existed, [[NSFileManager defaultManager] fileExistsAtPath:fileName] = YES.

Conclusion, upon relaunch of an app wanting to open an existing iCloud document, you must perform both methods:

id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
NSURL *myUbiquityContainer = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier: nil];

This will allow you to access the actual document in the fully qualified path URL, in my case saved from a previous session in the app.

Greg Walters
  • 181
  • 5