iCloud and UIDocument: Beyond the Basics, Part 1/4

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. Creating a document-based iCloud app is complicated. There’s a lot of different things to think about, and it’s easy to forget to implement something out or make a mistake along the way. There are several tutorials on creating iCloud […]

Version

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

Creating a document-based iCloud app is complicated. There’s a lot of different things to think about, and it’s easy to forget to implement something out or make a mistake along the way.

There are several tutorials on creating iCloud apps out there, including Apple’s Your Third iOS App tutorial and our own iOS 5 by Tutorials, which do a great job of explaining the basics of using iCloud.

What we really need is a tutorial that goes beyond the basics and puts everything together into a complete app that you (gasp) might actually be able to use. This is my attempt to put something like that together for you guys!

In this tutorial, we’re going to create a simple iCloud document based app called PhotoKeeper. It will show you how to do the following:

This is a four-part tutorial series. In the first two parts, we’ll get things working for local documents only, and then in the second two parts, we’ll add iCloud support.

Are you ready to see how to put everything together with iCloud? Let’s get started!

Choosing our iCloud Storage Method

In this tutorial, we are going to create a simple app called PhotoKeeper that allows users to store and name their favorite photos either locally or in iCloud.

There are three main ways an app can store its data in iCloud. Which should we choose for this app?

Use Key/Value Store. iCloud has an extremely easy to use helper class called NSUbiquitousKeyValueStore that is very similar to NSUserDefaults. If your app has a small amount of data to store (< 1 MB), this method is usually the best choice.

Use Core Data. You can set up Core Data to store its data on iCloud. If your app doesn’t have the concept of documents or files but has a large amount of data, this method is usually the best choice.

Create a Document-Based App. The final method is to create a document based app by subclassing a class called UIDocument. If your app is based around the concept of individual documents that the user can create/read/update/delete that you want listed as separate files in Settings, this method is usually the best choice.

For PhotoKeeper, the third option makes the most sense. We want each photo treated as a separate unit/document/file that we can see in iCloud storage separately.

If you think Key/Value Storage or iCloud makes more sense for your app, check out iOS 5 by Tutorials, where we have some examples and more details on those techniques.

iCloud Document-Based App Overview

Next let’s give an overview of what it takes to make an iCloud Document-Based App, and some design choices we’re going to make for PhotoKeeper along the way. We will cover five topics:

How it Works

Subclassing UIDocument

Input/Output Formats

Local vs. iCloud

Storage Philosophy

1) How it Works

At a very high level, here’s how iCloud and UIDocument works.

If iCloud is available and your app is configured to use iCloud, your app can access a special directory on the device. Whatever you put in this directory will be automatically synchronized to iCloud by a system daemon.

You can’t just enumerate this directory with normal APIs – you have to use a special API we’ll discuss later. You also have to be careful about accessing files in this directory, because the the iCloud daemon is also working with the files at the same time.

The easiest way to work with these files is by subclassing a class called UIDocument. It takes care most of the details for you (such as coordinating with the iCloud daemon and more), and allows you to focus primarily on what matters to you – your app’s data!

loadFromContents:ofType:error: Think of this as “read the document”. You get an input class, and you have to decode it to your internal model.

contentsForType:error: Think of this as “write the document”. You encode your internal model to an output class.

It’s usually best if your UIDocument class contains a reference to your app’s model class, which can be just a plain old NSObject. If your model object supports NSCoding (tutorial here), implementing these methods is trivial.

For this tutorial, we’ve kept our document’s data model very simple – it’s literally jsut a photo! However, you can use this same technique we’re demonstrating in this tutorial no matter how complex or large your docuemnt is.

3) Input/Output Formats

UIDocument supports two different classes for input/output:

NSData. This represents a simple buffer of data, which is good when your document is just a single file.

NSFileWrapper. This represents a directory of file packages, which the OS treats as a single file. This is good for when your document consists of multiple files that you want to be able to load independently.

At first glance, it might seem that using NSData would make the most sense, since our document is just a simple photo.

However, one of the design goals of PhotoKeeper is to show a thumbnail of the photo in the master view controller before the user opens a file. If we used NSData, to get the thumbnails we’d have to open and decode every single document from the disk. Since the images can be quite large, this could lead to slow performance and high memory overhead.

So what we’re going to do instead is use NSFileWrapper. We’ll store two documents inside the wrapper:

The main data. This will be the main data of the document. For PhotoKeeper, this will be our full size photo.

The metadata. This will be anything the master view controller needs to display a preview. It should be a small amount of data that can be loaded quickly. For PhotoKeeper, this will be a small (resized) thumbnail of the photo.

This way the master view controller only needs to load each photo’s (small) metadata sub-file inside the NSWrapper instead of the entire (large) data sub-file. It might not make a huge difference in this tutorial, but the larger your document’s data file becomes the more useful this is!

4) Local vs. iCloud

It’s not enough to make an app that is just iCloud enabled – it also has to work without iCloud too!

This is because the user might not have iCloud enabled on their device, or may want to turn iCloud off specifically for your app.

One of the nice things about using UIDocument is you can use it for both local storage and iCloud storage, so you don’t have to have two widely different code paths.

However, adding iCloud support with UIDocument does have a lot of extra steps and subtle problems to address. So I personally find it easier to get your app working locally with UIDocument, and then adding iCloud support once that’s working. So that’s what we’re going to do in this series!

5) Storage Philosophy

It is theoretically possible to design an app that has two different tabs – iCloud and local – and allow users to select where to store individual documents.

All documents of an application are stored either in the local sandbox or in an iCloud container directory. A user should not be able to select individual documents for storage in iCloud.

The same document also recommends that users have an option to disable iCloud for your app. I think the best place for this is in Settings, since it’s a setting that only needs to be changed rarely, reduces the clutter in your app, and discourages users from messing with it once they have it set up.

Putting it All Together

Phew – that’s a lot of background information! Don’t worry if you don’t understand everything right away, it will become more clear as we work through the tutorial.

To sum up what we discussed above, we will be doing the following for PhotoKeeper:

Choosing a Document-Based App as our storage method.

Creating a model class (a plain old NSObject that stores our large photo) that implements NSCoding.

Creating a metadata class (a plain old NSObject that stores our small thumbnail) that implements NSCoding.

Subclassing UIDocument. It will have a reference to the model and metadata classes.

Using NSFileWrapper for input/output. Our UIDocumentClass will encode both the model and metadata into separate files inside our NSFileWrapper directory.

Work both locally and for iCloud. We will start with local only and add iCloud support later in the series.

Add iCloud on/off toggle in Settings. All documents will be stored either locally or in iCloud.

Note that for your apps you might make different design decisions than the above – however if you’re UIDocument based most of this tutorial will still apply, so read on :]

Getting Started

It’s finally time to code!

Create a new project in Xcode and choose the iOS\Application\Master-Detail Application tepmlate. Enter PhotoKeeper for product name, PTK as the Class Prefix, select iPhone for the Device Family, and make sure Use Storyboards and Use Automatic Reference Counting are checked (but nothing else).

Next, download the resources for this project and add them to your Xcode project. These are just two simple classes to help format a string or resize images and a few images – take a peek if you want.

Compile and run the project, and you’ll see the template has generated a skeleton of a simple master/detail app. You can tap the + button to add entries, and tap an entry to go to a detail screen.

Our goal in this first part of the series is to switch the master view to showing a list of local UIDocuments, and allow the + button to create a new UIDocument.

Before we can create a UIDocuemnt subclass though, we need to create our model classes.

Let’s start with the main model class. Create a new file with the iOS\Cocoa Touch\Objective-C class template, name it PTKData, and make it a subclass of NSObject. Then replace the contents of PTKData.h with the following:

As you can see, this is a very simple model class that only has one piece of data to track – the full size photo. It implements the NSCoding protocol to encode/decode to a buffer of data.

Note that it stores a version number along with the photo. This makes it easy to update the data structure if you want to in the future while still supporting older files. If you ever add a new field, you bump up the version number, then while decoding you can check the version number to see if the new field is available.

This is pretty much exactly the same as PTKData, so no need to discuss further here. We even could have used the same class, but it’s better to keep things separate in case we need more data fields in the future (which will be the case for most apps).

Congrats – now you have the model classes for PhotoKeeper!

Subclassing UIDocument

Next we are going to create a subclass of UIDocument that will combine the data and metadata into a single NSFileWrapper.

Create a new file with the iOS\Cocoa Touch\Objective-C class template, name it PTKDocument, and make it a subclass of UIDocument. Then replace the contents of PTKDocument.h with the following:

Even though NSFileWrapper is basically a directory, we still need to give it a file extension so we can identify the directory as a document our app knows how to handle. We’ll use “ptk” as our file extension, so we define it here for easy access later.

Rather than letting users of PTKDocument access the PTKData directly, it’s best if you add accessors instead. This is because UIDocument is built around the concept of undo/redo – it has an undo manager and when you make changes UIDocument can auto-save in the background. Accessors are the perfect spot to register your undo actions, which you’ll see in a minute.

It’s OK if the user accesses the metadata directly though, as it’s not something the app will modify. Instead, the metadata will be automatically updated when the user sets the photo.

Next open PTKDocument.m. There’s a lot of code we’re going to add here, so let’s go over it bit by bit.

First we import the headers we need and define the filenames for the two sub-files that will be inside our directory/NSFileWrapper.

Then we create two private properties – one for the main document model class (PTKData), and one for the NSFileWrapper (the class that treats the directory as a single file). Finally we synthseize our properties.

Take a look at contentsForType first. To create a directory NSFileWrapper, you call initDirectoryWithFileWrapper and pass in a dictionary that contains file NSFileWrappers as the objects, and the names of the files as the key. We use a helper method encodeObject:toWrappers:preferredFilename to create a file NSFileWrapper for the data and metadata.

The encodeObject:toWrappers:preferredFilename method will look familiar if you are have used NSCoding in the past. It uses the NSKeyedArchiver class to convert the object that implements NSCoding into a data buffer. Then it creates a file NSFileWrapper with the buffer and adds it to the dictionary.

Sweet! Now let’s implement reading. Add the following to the file next:

Start at the bottom with loadFromContents:ofType:error:. Notice how all we do is squirrel the passed in contents (a directory NSFileWrapper, since that’s what we saved) into a property. We don’t actually load the data right away (although you can do that for your app if you want), since for this app we want to lazy load instead.

Lazy loading is a fancy way of saying “don’t load it until I ask for it.” This is good for our app because the master view controller only needs to load the metadata. The full data is only needed when you go to the detail view controller.

If you look at the accessors for metadata and data, they check to see if there is a directory NSFileWrapper stored, and if there is they read the appropriate file from the directory NSFileWrapper.

Finally, decodeObjectFromWrapperWithPreferredFilename does the opposite of encodeObject:toWrappers:preferredFilename. It reads the appropriate file NSFileWrapper from the directory NSFileWrapper and converts the NSData contents back to an object via the NSCoding protocol.

Description returns the filename without the path or extension, and photo is a simple getter.

The important part is setPhoto. Not only does this set the photo in the data, but this also creates a smaller thumbnail image and saves it in the metadata.

It also registers an undo action so that the user can undo the change (via shaking on the iPhone, or you can add a custom button). By doing this, internally UIDocument now knows that the document has been modified, so it will auto-save periodically in the background.

w00t – now you have both the data model and UIDocument working! Now it’s time to retrofit our master view controller to use it.

Creating Documents

Before we can display a list of documents, we need to have the ability to add at least one so we have something to look at. So let’s start by imiplementing creating a new document.

There are four things you need to do in order to create a new document in this app:

Store Entries

Find an Available URL

Create the Document

Make Final Changes

Let’s go over these one by one!

Storing Entries

If you look at PTKMasterViewController.m, you’ll see that viewDidLoad adds a button to the toolbar that the user can tap to create a new entry:

When the button is tapped, insertNewObject is called. The current implementation puts an NSDate in an array, which is displayed in a table view.

Instead of displaying NSDates, we want to display information about our documents. For each document, we’ll need to know its file URL, its metadata (thumbnail), the date it was last modified, and what state the document is in (more on this later).

To keep track of all of this information we need, let’s create a new class real quick. Create a new file with the iOS\Cocoa Touch\Objective-C class template, name it PTKEntry, and make it a subclass of NSObject. Then replace the contents of PTKEntry.h with the following:

The next step is to figure out the full URL for where we want to create the document. This isn’t as easy as it sounds, because we need to auto-generate a filename that isn’t already used. And the first step of doing that is having a way to check if a file exists.

To help solve this, let’s assume that the _objects array contains all the PTKEntries on the disk. Then all we have to do is look through this array to see if the name is already taken.

Let’s see what this looks like in code. Make the following changes to PTKMasterViewController.m:

Let’s start from the top. We start by adding some imports we need, and add instance variables to keep track of the local root (i.e. the Documents directory) and the selected document when a user taps a row. We also define a method to return whether iCloud is on or off. For now we set it to always off, but we’ll come back to this later.

Next we write a getter for localRoot so it can be lazy loaded. If it isn’t set yet, we use find the URL for the documents directory and squirrel it away in the instance variable.

getDocURL is a method where you pass in a filename and get the full path to the file. If iCloud is off, this simply appends the filename to the Documents directory path. We’ll add the iCloud implementation later.

Finally, docNameExistsInObjects loops through the list of PTKEntry instances in _objects and checks to see if the name matches any of them. If it does, there’s a conflict, otherwise it’s OK.

This starts with the document name passed in and checks if it is available. If not, it adds 1 to the end of the name and tries again. If that fails it keeps incrementing the number until it finds an available name.

Creating a Document

To create a PTKDocument, there are two steps:

Alloc/init the PTKDocument with the URL to save the file to.

Call saveToURL to initially create the file.

After you call saveToURL, the document is effectively “opened” and in a usable state.

After we create the document, we need to update the objects array to store the document, and display the detail view controller.

Let’s add the code! First we need to add a helper method to add (or update) a PTKEntry in the _objects array. Add the following code right before awakeFromNib:

We’re finally putting all of the helper methods we wrote to good use! Here we:

Use getDocURL to find the local Documents directory

Use getDocFilename to find an available filename in that directory

Alloc/init a PTKDocument

Call saveToURL to save it right away

Keep track of the (opened) and selected document

Add the entry to the table, and perform the showDetail segue

Note that there is no guarantee that the completionHandler passed to saveToURL executes on the main thread, hence we have to use dispatch_async to run parts of the code on the main queue which need it.

Final Changes

OK, almost ready to test this out! Just make these final changes to PTKMasterViewController.m:

Here we just initialize the objects array and configure our table view to display information about the documents. We’ll fix up the segue and detail view controller later.

Compile and run your project, and you should now be able to tap the + button to create new documents:

If you look at the console output, you should see messages showing the full path the documents are being saved to, like this:

File created at file://localhost/Users/rwenderlich/Library/Application%20Support/
iPhone%20Simulator/5.1/Applications/
194CFF63-BE3F-4CAF-90CC-BFA843C2169B/Documents/Photo%202.ptk

However this app has a big problem. If you run the app again, nothing will show up in the list! That’s because we haven’t added the code to list documents yet, so let’s do that now.

Listing Local Documents

To list local documents, we’re going to get the URLs for all the documents in the local Documents directory, and open up each one. We’ll read the metadata out to get the thumbnail (but not the data, so things are efficient), then close it back up again and add it to the table view.

First let’s add the method that loads a document given a file URL. Add this right above the “View lifecycle” section with awakeFromNib:

Notice that we open the document, get what we need, and close it again rather than keeping it open permanently. This is for two reasons:

Avoid the overhead of keeping an entire UIDocument in memory when we just need one part

UIDocuments can only be opened and closed once (!) UIDocument is a “one shot” class – you can just open it once and close it once. If you want to open the same fileURL again, you have to create a new UIDocument instance.

This is pretty simple file management stuff - loadLocal calls loadDocAtURL for everything in the Documents directory, and refresh kicks this all off.

The reason we enable/disable the right bar button item is you shouldn't be able to create a new file until we have retrieved the list of files. This is because we need the list of files to guarantee a unique name when creating a new file. This doesn't much matter now since we get the list of files right away, but is good to have in place for when that isn't the case with iCloud.

To try this out, add the following to the bottom of viewDidLoad:

[self refresh];

Compile and run, and this time your app should correctly pick up the list of documents since last time it was run!

Where To Go From Here?

At this point, you have the data model and UIDocument wrapper working and the ability to create and list files. It might not look like much yet, but that's the most critical part!

In the next part of the series, we'll get this app fully functional and polished when it comes to local files. We'll customize our table view cells, add a detail view, support deleting and renaming, and more! At that point, you'll be fully set to move to the iCloud. :]

If you have any questions, comments, or suggestions about better ways to do things, please join the forum discussion below! :]

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

Contributors

Ray is part of a great team - the raywenderlich.com team, a group of over 100 developers and editors from across the world. He...

Author

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. Creating a document-based iCloud app is complicated. There’s a lot of different things to think about, and it’s easy to forget to implement something out or make a mistake along the way. There are several tutorials on creating iCloud […]