This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

Welcome back to our document-based iCloud app tutorial series!

In this tutorial series, we are making a complete document-based iCloud app called PhotoKeeper, with features that go beyond just the basics.

In the first and second parts of the series, we made a fully-functional, UIDocument-based app that works with local files with full CRUD support.

In this third part of the series, it’s finally time to dive into iCloud! We will get almost everything working on iCloud in this tutorial, except for some subtle bits which we’ll leave for the final part of the series.

This project continues where we left off last time, so if you don’t have it already grab the previous code sample and open up the project. It’s iCloud time, baby!

Checking if iCloud is Available

Before you can use iCloud, you first need to check if it is available by calling NSFileManager’s URLForUbiquityContainerIdentifier method.

This method also initializes iCloud for you, and returns the URL of the “iCloud directory” – that is, the directory that that the iCloud daemon checks for files to sychronize to the cloud.

This call might block so it’s important to call it from a background thread. Let’s try it out. Open PTKMasterViewController.m and make the following changes:

The most important part here is that we call URLForUbiquityContainerIdentifier to figure out where the iCloud root directory is. If it returns nil, that means iCloud isn’t available.

Notice we take a block as a parameter – it’s a method we’ll call after URLForUbiquityContainerIdentifier completes to indicate whether or not iCloud is available. If you’re a bit rusty on blocks, you might want to check out our blocks tutorial.

Now let’s try this out. Go to the refresh method and replace it with the following:

The only difference here is we wait until we check if iCloud is available before refreshing our list of files.

Compile and run the app on your device – not the simulator, because iCloud doesn’t work on the simulator. You should see this in the console:

iCloud not available

You should see this whether you have iCloud enabled or not in Settings. What gives?

This is because for your app to use iCloud, you have to do several project configuration steps. Let’s get to it!

Configuring your Project for iCloud

The first step is to visit the iOS Developer Center and go to the iOS Provisioning Portal. Click App IDs in the sidebar and then New App ID. Create an App ID for your app, similar to the following:

After you are done, you will see your App ID in the list:

Note that iCloud is not enabled by default – you have to configure it. To do so, simply click the Configure button, check the checkbox for Enable for iCloud, and click Done.

Next you need to create a provisioning profile for your App ID. Click the Provisioning tab, click New Profile, and select the appropriate information like you can see below.

After you finish creating the profile, refresh the page until it is available for download, and download it. Double click it to install it in Xcode. You should see it in the Organizer when you’re done:

Next, you need to set up your Xcode project to use this provisioning profile. Click your project in the Project Navigator, select the PhotoKeeper target, adn go to the Build Settings tab. Search for code sign, and set the code signing identity to your new provisioning profile.

To use iCloud, you need to set up some Entitlements that gives your app access to the iCloud directory. This used to be a pain but now it’s extremely simple. Just go to the Summary tab and click the checkbox for Enable Entitlements. It should autofill everything else, but sometimes I’ve seen it not fill out the iCloud Containers section, so you might have to click the plus button and add it if so.

The last step is you need to configure your app to recognize the file types for the documents the app is storing. This isn’t absolutely required at this point, but you will run into troubles if you don’t do it later so you might as well do it now.

Switch to the Info tab and click the plus button in the bottom right. In the pop-up, select Add Document Type.

You will see that it has created a new entry for you in the Document Types list above. Fill it out similarly to this example:

Next click the plus button again but select Add Exported UTI this time.

It will create a new entry for you in the Exported UTIs list above. Fill it out similarly to this example:

It is important that whatever you put for Identifier here matches what you put for Types in the Document Types. Also, I have had trouble with filename extensions that weren’t exactly 3 characters in length, so if you have any troubles in your app try working with 3 character extensions.

Phew – finally done! Compile and run your app, and if all works well you should now see something like this in the console:

This gives you the directory on the device where you can find and create iCloud files. The iCloud daemon will automatically pull down new files to this directory as they become available, update files that are there, watch for changes you put there, etc.

We’ll use this directory a lot in this tutorial, but before we go we have the first tricky bit about iCloud to discuss – turning iCloud on and off.

iCloud On, iCloud Off

Like we mentioned back in part 1, you shouldn’t just assume you should use iCloud if it’s available – you should allow the user to enable/disable iCloud for your app.

And also like we discussed before, I believe the best place for this configuration option is in Settings (rather than in-app settings). This way it reduces clutter in your app and discourages users from changing it unnecessarily.

Adding a switch to turn iCloud on and off is easy, so let’s deal with that first. Create a new file with the iOS\Resource\Settings Bundle template, and name it Settings.bundle. Open Settings.bundle\Root.plist, and modify the file to look like the following:

Compile and run the app on your device, and switch to the Settings app. Scroll down to find the category for PhotoKeeper, and you should see a switch for iCloud:

Settings is pretty cool because getting the value for this switch is as easy as reading the “iCloudOn” key from NSUserDefaults.

Now that we’ve got a switch, it starts getting tricky. We have to make sure we have code that handles all of the following cases:

If iCloud isn’t switched on, but it’s available (and we haven’t bugged the user already), ask the user if they want to turn on iCloud. This way they don’t necessarily have to go to Settings at all.

If iCloud isn’t available, but it was on before, warn the user that although the app can’t access their iCloud files anymore, they’re still out on the cloud.

If iCloud was switched on (but wasn’t previously), that means we need to move our local files to iCloud.

If iCloud was switched off (but wasn’t previously), that means we need to ask the user what to do with the old iCloud files. The user might want to leave them on iCloud, keep a copy, or change their mind and turn iCloud back on again.

To accomplish all this, in addition to the “iCloudOn” flag managed by Settings, we need to create two more NSUserDefaults flags – one for if iCloud “used to be on”, and one for if we’ve prompted the user if they want to use iCloud yet.

Add these wrappers to PTKMasterViewController.m at the top of the “Helpers” section (overwriting the old iCloudOn method):

There’s a lot of code here, but none of it is very complicated – it’s just implementing the logic discussed earlier. Be sure to read through the refresh method to make sure you understand the basic logic there.

You may wonder why we’re calling “startQuery” even if iCloud is off. We’re going to need valid a list of iCloud files for when we implement the iCloudToLocal method. But don’t worry, we won’t display the list if iCloud is off. More on this later.

There’s one final step. Since the user can leave the app and change the value in Settings at any time, we need to refresh whenever the user returns to the app (i.e. the app “didbecomeactive”). So make the following changes to listen for that event:

Make sure iCloud is off and create a local file. Switch to settings and turn iCloud on. You should no longer see the file.

Make sure iCloud is on and go to Settings\iCloud\Documents and Data and switch it off. This effectively makes iCloud not available. Switch back to the app and you should get a popup, and it should switch to iCloud off automatically.

Switch back to Settings and make iCloud available again. Switch back to the app and it will tell you iCloud is available and ask if you want to turn it on.

Play around with switching iCoud on and off, ana make sure the appropriate “local => iCloud” or “iCloud => local” messages print out.

Nice! Now that we have this firm framework in place, all we really need to care about from here on out is whether “iCloudOn” is YES or NO. If YES, we should be displaying iCloud files, otherwise local files.

But we haven’t covered yet how to get a list of iCloud files. Let’s cover that now!

Querying iCloud Files

As we mentioned in part 1, you can’t just enumerate the iCloud directory with NSFileManager APIs like you do for the documents directory. Instead, you have to use a fancy API called NSMetadataQuery.

This API lets you set up a search on a directory, such as “give me all files that end with PTK”. It will return the initial results, and then continue to watch the directory for you, sending you update notifications as they come in. It’s pretty handy and efficient in comparison to the alternative (periodic polling).

Let’s see what it looks like. Go to the “iCloud Query” section and add this right above startQuery:

When you create a NSMetadataQuery, you can tell it to watch the “Documents” subdirectory of the iCloud root (this is where we’ll be storing our files) by passing in NSMetadataQueryUbiquitousDocumentsScope as the search scope.

You can then filter the output with a predicate. We set up one here to look for files that end with the PTK extension.

Next let’s create some methods to start and stop this query. Make the following changes to PTKMasterViewController.m:

startQuery gets the query and starts it up. It also registers for two notifications – the initial gather notification, as well as further updates. In both cases, processiCloudFiles will be called – we’ll write that next. stopQuery just does the opposite and tears things down.

When you’re processing the results of an NSMetadataQuery, it’s important to stop the query so you don’t get more updates while you’re in the middle of processing the first, so we do that at the beginning and end of the method.

We then loop through the query results, and add everything to the list except for items that are hidden. We add all of the results to the most up-to-date list of _iCloudURLs, which we will find handy later, and set a flag indicating that they are ready.

If iCloud isn’t on, that’s all we do. But if it is on, we make sure our local list of entries matches the list of iCloud files. We first loop through our entries and remove anything not in the list of iCloud files by calling removeEntryWithURL. We also add anything that isn’t already in the list to the list by calling loadDocAtURL. When this is all done it’s safe to add a new document again, so we enable the right bar button item.

This is important to add because UIDocument has issues if you try to open more than instance at a time pointing to the same fileURL, so we don’t want our table view trying to refresh itself (hence loading a UIDocument to get its medatata) while we’re editing a document.

We could run the app now but there wouldn’t be any use, as there’s no way to create a file in iCloud yet. Luckily, since we already laid most of the groundwork by using UIDocument this is quite easy to fix! Simply replace getDocURL with the following:

Here we’ve implemented the “iCloudOn” case to return the Documents subdirectory of the iCloud root, where we will be saving our files.

When we go to create a new file, now it will be creating it in the iCloud directory, so the daemon will pick up on it and synchronize it. Everything else will just work since we’re using UIDocument!

Compile and run on your device, and make sure iCloud is available and switched on. Create some documents and verify that they are created in the iCloud directory by looking at the logs.

Now for the fun part – start up a second device and you should see the same files pull down from iCloud! :D

If you have any troubles, try deleting the app from your device and re-installing, double checking that your Document exports are set up properly, or compare your project to the solution at the end of this tutorial.

Fixing Deleting

When you’re dealing with files in an iCloud directory, you can’t just use NSFileManager APIs directly like you could with local files. Instead, you have to use the NSFileCoordinator class to make sure it’s safe to modify the files. This is important since the iCloud daemon is also working with this directory at the same time.

To see what this looks like, open PTKMasterViewController.m and replace deleteEntry with the following:

Here we create a NSFileCoordinator and use the coordinateWritingAtURL method passing in NSFileCoordinatorWritingForDeletign to make sure we have access to delete the file. This method may block, so it’s important to run it on a background thread. Once we have access, it’s safe to delete it as usual.

Compile and run, and now you should be able to delete entries – but safely this time!

Fixing Renaming

Similarly, we need to fix up renaming. In the renameEntry method, replace the “Simple renaming to start” section with the following:

To be honest, I have no idea why we get this error – it seems to me that this should work. If anyone knows (maybe I have passed the wrong parameters to coordinateWritingItemAtURL:entry.fileURL?) let me know.

Update: Jim Tobin wrote in to let me know that he got this to work by creating a copy of entry.fileURL and passing that in instead. I haven’t had a chance to verify this yet, but feel free to give it a try!

In the meantime, I have another method that works (although is slightly hackish) – save the old file to a new filename, and delete the old file. Try it out by replacing the above code block with the following:

Note that the way the app is currently designed, when you rename a file it will disappear temporarily (because the old file was deleted) and reappear a second later (as the new file is noticed and opened). This is because our table view is refreshed upon the results of the NSMetadataQuery. If anyone has a better strategy to deal with updates to avoid this problem, please drop a note in the forum discussion!

Detecting Updates

The last thing we’ll cover in this part of the tutorial is detecting updates to an opened document. Right now, if you open the same document on two devices and change the photo on one of the devies, the other device won’t notice that it’s changed.

Luckily, UIDocument has built-in support to notice changes and update itself behind the scenes! All we need to do is register for a notification when this occurs so we can update the detail view controller.

Open PTKDetailViewController.m and add these methods right after shouldAutorotateToInterfaceOrientation:

Where To Go From Here?

At this point, this app is fully functional for local documents, mostly functional for iCloud documents (with full create, read, update, delete support), and allows you to toggle back and forth between the two options in Settings.

However, there are still some subtle aspects remaining that we need to address. We need to implement those methods to move documents to and from iCloud when the user switches iCloud on or off, and we need to deal with the dreaded iCloud conflicts!

So when you’re ready, move on to the final part of this tutorial series, where we will cover those topics. In the meantime, if you have any comments, questions, or suggestions for improvement, please join the forum discussion below!

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

Ray is part of a great team - the raywenderlich.com team, a group of over 100 developers and editors from across the world. He and the rest of the team are passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

Is there some delay in the changes made to options via the Settings app in IOS and an app that reads then when ir becomes active?

I used the method used in this tutorial for my app whereby inside the app there is no means for the user to interact with the option settings, they have to leave the app and make the changes via the Settings app then return to my app.

However I notice that intermittently I have to do this to see the change to an option take affect in my apps view:

Open my app and navigate to a view that has a textview with a font of size 50
Press Home
Open Settings
Change font size to 12
Press Home
Open my app and the font auto changes to 12

PERFECT

But.....another time I do:

Open my app and navigate to a view that has a textview with a font of size 50
Press Home
Open Settings
Change font size to 12
Press Home
Open my app and the font has not changed?
Press Home
Open the app again....and the font has changed!

So it looks like sometimes the change made via the Settings app does not get reflected as I call to load them in my app?

@elpuerco63 did you get past this? I think you want to ensure your viewDidLoad is setting up an observer for the notification UIApplicationDidBecomeActiveNotification and that in the method that gets called when that notification arrives you are checking NSUserDefaults and applying any changed values.

Regarding the Settings Bundle, Is it possible to put the iCloud on/off switch on the Settings->iCloud page? It seems like a natural place for it. I have searched without any luck as to if it is possible and how it would be done.

I have a UIDocument named "Old". I've made some changes to it and would like to re-save it under the name "New". The contents of "Old" should not change. Here is my failed attempt:

First, copy the yet unsaved "Old" document to "New" file location. Save "Old" document to "New" file location. In theory, because the "Old" document was never saved to its URL in the file system, it should still be in its "Old" state. The "New" file location should have the recent changes to "Old" because we just saved the "Old" document to the "New" file location.

Regarding that error in the file move/rename, I know it's been 2 years, but I'm going to comment anyway... The current documentation says to make sure and use the newURL1 and newURL2 values, so maybe that was the issue. I am using the same code as above, except I'm using newURL1 and newURL2 as the variables in the moveItemAtURL, and it works fine for me.