显示标签为“iCloud”的博文。显示所有博文
显示标签为“iCloud”的博文。显示所有博文

2011年11月21日星期一

iOS Code: MKiCloudSync – Sync your NSUserDefaults to iCloud with a single line of code

iOS Code: MKiCloudSync – Sync your NSUserDefaults to iCloud with a single line of code:
Just wrote this class, MKiCloudSync (100 lines of code) that auto syncs your NSUserDefaults to iCloud.

How to use?


All you need to do is to enable iCloud key value store entitlements, copy the files and forget about the rest.

Step 1:

Enable iCloud entitlements for your app. This is easily done in Xcode 4.2.1 by opening your target settings and checking “Enable Entitlements” from the summary tab.

This is illustrated below.

Xcode
Enabling iCloud Entitlements in Xcode 4.2.1


Tip:

If you are sharing settings with other apps, ensure that your iCloud Key-Value Store is the same across all those apps.

Step 2:

Drag the two files

  • MKiCloudSync.h
  • MKiCloudSync.m

into your project. You can find these files in the Source Code section of this post.

Step 3:

In your applicationDidFinishLaunchingWithOptions: method,

start the sync by calling,


[MKiCloudSync start];


You also have to #include the header file.

This is probably the only line of code you have to write!

Step 4:

Sleep… Kidding.

There is no step 4. Continue using NSUserDefaults to save your settings. The MKiCloudSync class automatically syncs your settings to iCloud and restores them back to your NSUserDefaults when they are changed on other devices.

To top them all, it also posts a notification,

kMKiCloudSyncNotification

to let you know that a sync has been performed. You can listen to this notification and update your user interface from NSUserDefaults.

Advantages


  • Upgrading your existing iOS 4 apps to sync settings to iCloud is now super easy.
  • With this class, you no longer have to add calls to NSUbiquitousKeyValueStore throughout your app. You can continue your existing method of saving them to NSUserDefaults. Everything is automatic. If your settings might change often, you can observe the notification kMKiCloudSyncNotification and update your user interface.

Source Code



Licensing


Royalty free for commercial or non-commercial use, though attribution will please me to write more such code :)



Mugunth
©2011 MKBlog. All Rights Reserved.

2011年11月2日星期三

Beginning iCloud in iOS 5 Tutorial Part 2

Beginning iCloud in iOS 5 Tutorial Part 2:
Learn how to use iCloud in iOS 5!
Learn how to use iCloud in iOS 5!

Note from Ray: This is the tenth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Enjoy!

This is a post by iOS Tutorial Team member Cesare Rocchi, a UX designer and developer specializing in web and mobile applications.

This is the second part of a two-part tutorial series on how to get started using iCloud in iOS 5.

In the first part of the tutorial series, we covered how iCloud works and how to open and save a UIDocument programatically.

In this part of the tutorial, we’ll add a user interface to our app and cover how to work with multiple documents.

This tutorial continues where the first part left off, so be sure to go through it first.



Setting Up the User Interface


The Xcode project template we chose already set up an empty view controller for us. We will extend it by adding the current document and a UITextView to display the content of our note.

Start by modifying ViewController.h to look like the following:


#import <UIKit/UIKit.h>
#import "Note.h"

@interface ViewController : UIViewController <UITextViewDelegate>

@property (strong) Note * doc;
@property (weak) IBOutlet UITextView * noteView;

@end


We have also marked the view controller as implementing UITextViewDelegate so that we can receive events from the text view.

Next, open up ViewController_iPhone.xib and make the following changes:

  • Drag a Text View into the View, and make it fill the entire area.
  • Control-click the File’s Owner, and drag a line from the noteView outlet to the Text View.
  • Control-click the Text View, and drag a line from the delegate to the File’s Owner.

At this point your screen should look like this:

Adding a text view into Interface Builder

When you are done, repeat these steps for ViewController_iPad.xib as well.

Next, open up ViewController.m and synchronize your new properties as follows:


@synthesize doc;
@synthesize noteView;


Then modify viewDidLoad to register for the notification our code will send when our document changes (we’ll add the code to send this notification later):


- (void)viewDidLoad
{
[super viewDidLoad];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(dataReloaded:)
name:@"noteModified" object:nil];
}


Next, implement the method that gets called when the notification is received as follows:


- (void)dataReloaded:(NSNotification *)notification {

self.doc = notification.object;
self.noteView.text = self.doc.noteContent;

}


This simply stores the current document and updates the text view according to the new content received.

In general substituting the old content with the new one is NOT a good practice. When we receive a notification of change from iCloud we should have a conflict resolution policy to enable the user to accept/refuse/merge the differences between the local version and the iCloud one. We’ll discuss more about conflict resolution later, but for now to keep things simple we’ll just overwrite each time.

Next, implement textViewDidChange to notify iCloud when the document changes, and modify the app to refresh the data in viewWillAppear as well:


- (void)textViewDidChange:(UITextView *)textView {

self.doc.noteContent = textView.text;
[self.doc updateChangeCount:UIDocumentChangeDone];

}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.noteView.text = self.doc.noteContent;
}


As above this is not a great practice, because we are going to notify each iCloud about every single change (i.e. each time a character is added or deleted). For efficiency, it would be better to just tell iCloud every so often, or when the user has finished a batch of edits.

There’s just one last step remaining – we need to add the code to send the “noteModified” notification we registered for in viewDidLoad. The best place in this case is the Note class’s loadFromContents:ofType:error, method which is called whenever data are read from the cloud.

So open up Note.m and add this line of code to the bottom of loadFromContents:ofType:error (before the return YES):


[[NSNotificationCenter defaultCenter]
postNotificationName:@"noteModified"
object:self];


Now we are really ready! The best way to test this application is the following: install it on two devices and run it on both. You should be able to edit on one and see the changes periodically propagated to the other.

The propagation of changes is not immediate and might depend on your connectivity. In general, for our examples, it should take 5-30 seconds. Another way to check the correctness it to browse the list of files in your iCloud.

It is a bit hidden in the menu. Here is the sequence:

Settings -> iCloud -> Storage and Backup -> Manage Storage -> Documents & Data -> Unknown

If the application works correctly you should see the note we created in our app:

iCloud document for app in Settings

The ‘unknown’ label comes from the fact that the application has not been uploaded and approved on the Apple Store yet.

Also note that users can delete files from iCloud from this screen at-will (without having to go through your app). So keep this in mind as you’re developing.

Congrats – you have built your first iCloud-aware application!

Handling Multiple Documents


Cool, our example works and we are a bit more acquainted with the capabilities of iCloud. But what we have right now isn’t enough to impress our users, or build an application that makes sense. Who wants to manage just one document?!

So next we are going to extend our application to manage more than one document at a time. The most natural development of our current prototype is to transform it into a notes application, as follows:

  • The application will start with a view showing a list of notes
  • Each note will have a unique id
  • Tapping a note will show a single note view with the content
  • Users can then edit the content
  • The list of notes is updated when we launch the application or tap a refresh button

We will reuse some of the code of the previous project but we will need to reorganize it. Let’s start by rearranging the user interface.

Reorganizing the User Interface


In our new project a single view is not enough, we’ll need two. The first will be a table view which shows the list of notes. The second will be pretty similar to the main view of the previous project: it will show an editable text view.

New architecture for our app

Let’s add a empty table view controller, and modify our app to show that first inside a navigation controller.

Create a new file with the iOS\Cocoa Touch\UIViewController subclass template, name the class ListViewController, and make it a subclass of UITableViewController. You can leave both checkboxes unchecked.

Then open AppDelegate.m, and import ListViewController.h at the top of the file:


#import "ListViewController.h"


Then replace the first few lines of application:didFinishLaunchingWithOptions with the following:


self.window =
[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ListViewController * listViewController =
[[ListViewController alloc] initWithNibName:nil bundle:nil];
UINavigationController * navController =
[[UINavigationController alloc] initWithRootViewController:
listViewController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];


This sets the app up to start with a navigation controller, with the new ListViewController as the first thing inside.

You can compile and run at this point, and you’ll see an empty table. A good start, but let’s make the table show our iCloud docs! Modify ListViewController.h to the following:


#import <UIKit/UIKit.h>
#import "Note.h"
#import "ViewController.h"

@interface ListViewController : UITableViewController

@property (strong) NSMutableArray * notes;
@property (strong) ViewController * detailViewController;
@property (strong) NSMetadataQuery *query;

- (void)loadNotes;

@end


Here we’ve added an array to store the notes, a reference to the old view controller we created we’ll be pushing onto the stack, a metadata query we’ll use to load the notes, and a loadNotes method we’ll write later.

Next switch over to ListViewController.m and synthesize properties at the top of the file:


@synthesize notes = _notes;
@synthesize detailViewController = _detailViewController;
@synthesize query = _query;


Then add the following code to the bottom of viewDidLoad:


self.notes = [[NSMutableArray alloc] init];
self.title = @"Notes";
UIBarButtonItem *addNoteItem = [[UIBarButtonItem alloc]
initWithTitle:@"Add"
style:UIBarButtonItemStylePlain
target:self
action:@selector(addNote:)];
self.navigationItem.rightBarButtonItem = addNoteItem;


This initializes the notes array to an empty list, sets up a title for this view controller, and adds a button to the navigation bar that says “Add”. When the user taps this, the addNote method will be called, and we’ll implement this to add a new note.

Unlike the previous project that had just one document (so always used the same filename each time), this time we’re storing multiple documents (one for each note created), so we need a way to generate unique file names. As an easy solution, we will use the creation date of the file and prepend the ‘Note_’ string.

So add the implementation of addNote as follows:


- (void)addNote:(id)sender {

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMdd_hhmmss"];

NSString *fileName = [NSString stringWithFormat:@"Note_%@",
[formatter stringFromDate:[NSDate date]]];

NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage =
[[ubiq URLByAppendingPathComponent:@"Documents"]
URLByAppendingPathComponent:fileName];

Note *doc = [[Note alloc] initWithFileURL:ubiquitousPackage];

[doc saveToURL:[doc fileURL]
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {

if (success) {

[self.notes addObject:doc];
[self.tableView reloadData];

}

}];

}


You should be pretty familiar with this code. The file name is generated by combining the current date and hour. We call the saveToURL method and, in case of success, we add the newly created note to the array which populates the table view.

Almost done with the ability to add notes – just need to add the code to populate the table view with the contents of the notes array. Implement the table view data source methods like the following:


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return self.notes.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
cell.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
}

Note * note = [_notes objectAtIndex:indexPath.row];
cell.textLabel.text = note.fileURL.lastPathComponent;

return cell;
}

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
self.detailViewController = [[ViewController alloc]
initWithNibName:@"ViewController_iPad" bundle:nil];
} else {
self.detailViewController = [[ViewController alloc]
initWithNibName:@"ViewController_iPhone" bundle:nil];
}
Note * note = [_notes objectAtIndex:indexPath.row];
self.detailViewController.doc = note;
[self.navigationController
pushViewController:self.detailViewController animated:YES];
}


Compile and run the application, and you should be able to add new notes when you tap the Add button. You can also go to the iCloud manager in settings to verify they are actually in iCloud.

But what if we quit the application and restart it? The list is empty! We need in fact a way to load them at startup or when the application becomes active.

Loading Notes


To load notes, we’ll follow a similar strategy to what we did earlier when loading a single note. However, this time we don’t know the exact file name, so we have to tweak our search predicate to look for a file name like “Note_*”.

So add this new method to ListViewController.m:


- (void)loadNotes {

NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];

if (ubiq) {

self.query = [[NSMetadataQuery alloc] init];
[self.query setSearchScopes:
[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:
@"%K like 'Note_*'", NSMetadataItemFSNameKey];
[self.query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:self.query];

[self.query startQuery];

} else {

NSLog(@"No iCloud access");

}

}


We might be tempted to place a call to this loadNotes method in viewDidLoad. That would be correct, but that is executed on initial app startup. If we want to reload data really each time the app is opened (even from the background), it’s better to add an observer to listen when the application becomes active.

So add the following line of code to the end of viewDidLoad:


[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loadNotes)
name: UIApplicationDidBecomeActiveNotification object:nil];


This calls the loadNotes method we just wrote on statup (or when the application returns from the background). And we set up loadNotes to call queryDidFinishGathering when the metadata search completes, so add the code for that next:


- (void)queryDidFinishGathering:(NSNotification *)notification {

NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];

[self loadData:query];

[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];

self.query = nil;

}


This is exactly the same as we added earlier in the tutorial. Next, add the implementation of loadData as follows (right above queryDidFinishGathering):


- (void)loadData:(NSMetadataQuery *)query {

[self.notes removeAllObjects];

for (NSMetadataItem *item in [query results]) {

NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
Note *doc = [[Note alloc] initWithFileURL:url];

[doc openWithCompletionHandler:^(BOOL success) {
if (success) {

[self.notes addObject:doc];
[self.tableView reloadData];

} else {
NSLog(@"failed to open from iCloud");
}

}];       
}   
}


The loadData method has to populate the array of notes according to the results of the query. The implementation here fully reloads the list of notes as returned by iCloud.

One final change. For testing purposes, go ahead and add a ‘refresh’ button to the navigator item which triggers the loadNotes method when tapped, by adding this code to the bottom of viewDidLoad:


UIBarButtonItem *refreshItem = [[UIBarButtonItem alloc]
initWithTitle:@"Refresh"                                                                
style:UIBarButtonItemStylePlain                                                             
target:self
action:@selector(loadNotes)];
self.navigationItem.leftBarButtonItem = refreshItem;


That’s it! Compile and run the app on one device, and create a few notes. Then start the app on another device, and see that the table is correctly populated with the same notes!

Where To Go From Here?


Here is a example project with all of the code from the above tutorial.

Congratulations, you now have hands-on experience with the basics of using iCloud and the new UIDocument class, to create an iCloud-enabled app with multi-document support.

We’ve just scratched the surface of iCloud. If you are interested in learning more about iCloud, check our our book iOS 5 By Tutorials – the final version will have an additional chapter that covers handling conflict resolution, using NSFileWrapper, storing simple key-value pairs, and using Core Data with iCloud!

If you have any comments or questions on this tutorial or on iCloud in general, please join the forum discussion below!

This is a post by iOS Tutorial Team member Cesare Rocchi, a UX designer and developer specializing in web and mobile applications. He runs Studio Magnolia, an interactive studio that creates compelling web and mobile applications.

Beginning iCloud in iOS 5 Tutorial Part 2 is a post from: Ray Wenderlich

2011年10月31日星期一

Beginning iCloud in iOS 5 Tutorial Part 1

Beginning iCloud in iOS 5 Tutorial Part 1:
Learn how to use iCloud in iOS 5!
Learn how to use iCloud in iOS 5!

Note from Ray: This is the ninth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Enjoy!

This is a post by iOS Tutorial Team member Cesare Rocchi, a UX designer and developer specializing in web and mobile applications.

We all have “stuff” we use on our iPhones and iPads regularly like documents, pictures, videos, emails, calendars, music, and address books. But how many times have you tried to quickly open a document and realized “argh, I have it saved onto another device”?

Well, with the new iCloud feature in iOS 5, that is problem of the past!

iCloud is a service that helps you synchronize your data across devices. It is a set of central servers which store your documents, and make the latest version available to every device/app compatible with iCloud (iPhone, iPod, iPad, Mac, or even PC).

In this tutorial, we’ll investigate iCloud by implementing a set of simple applications which interact with cloud servers to read, write and edit documents. In the process, you’ll learn about the new UIDocument class, querying iCloud for files, autosaving, and much more!

Note to get the most out of this tutorial, you will need two physical iOS devices running iOS 5 for testing, such as an iPhone and an iPad. The simulator does not currently have iCloud support.



Under the Hood


Before we begin, let’s talk about how iCloud works.

In iOS each application has its data stored in a local directory, and each app can only access data in its own directory. This prevents apps from reading or modifying data from other apps (although there are some alternate methods of transferring data between apps built into the OS).

iCloud allows you to upload your local data to central servers on the net, and receive updates from other devices. The replication of content across different devices is achieved by means of a continuous background process (daemon) which detects changes to a resource (document) and uploads them to the central storage.

This works real-time and enables another interesting feature: notifications. For example, whenever there is a conflict about a document, the application can be aware of that and you can implement a resolution policy.

If you ever tried to create something like this with your own apps, you know there are several major challenges implementing this:

  • Conflict resolution. What happens if you modify a document on your iPhone, and modify the same document on your iPad at the same time? You somehow have to reconcile these changes. iCloud allows you to break your documents into chunks to prevent many merge conflicts from being a problem (because if you change chunk A on device 1, and chunk B on device 2, since chunk A and B are different you can just combine them). For cases when it truly is a problem, it allows you as a developer fine-grained control over how to handle the problem (and you can always ask the user what they would like to do).
  • Background management. iOS apps only have limited access to running tasks in the background, but keeping your documents up-to-date is something you want to always be doing. The good news is since iCloud synchronization is running in a background daemon, it’s always active!
  • Network bandwidth costs. Continuously pushing documents between devices can take a lot of network bandwidth. As mentioned above, iCloud helps reduce the costs by breaking each document into chunks. When you first create a document, every chunk is copied to the cloud. When subsequent changes are detected only the chunks affected are uploaded to the cloud, to minimize the usage of bandwidth and processing. A further optimization is based on a peer-to-peer solution. That happens when two devices are connected to the same iCloud account and the same wireless network. In this case data take a shortcut and move directly between devices.

The mechanisms described so far are enabled by a smart management of metadata like file name, size, modification date, version etc. This metadata is pushed to the cloud, and iCloud uses this information to determine what needs to be pulled down to each device.

Note that devices pull data from the cloud when “appropriate”. The meaning of this depends on the OS and platform. For example an iPhone has much less power and “battery dependency” than an iMac plugged into a wall. In this case iOS might decide to notify just the presence of a new file, without downloading it, whereas Mac OS X might start the download immediately after the notification.

The important aspect is that an app is always aware of the existence of a new file, or changes to an already existing file, and through an API the developer is free to implement the synchronization policy. In essence the API allows an app to know the “situation” on iCloud even if the files are not yet local, leaving the developer free to choose whether (and when) to download an updated version.

Configuring iCloud in iOS 5


The first time you install iOS 5, you’ll be asked to configure an iCloud account by providing or creating an Apple ID. Configuration steps will also allow you to set which services you want to synchronize (calendar, contacts, etc.). Those configurations are also available under Settings\iCloud on your device.

Before you proceed any further with this tutorial, make sure that you have iOS 5 installed on two test devices, and that iCloud is working properly on both devices.

One easy way to test this is to add a test entry into your calendar, and verify that it synchronizes properly between your various devices. You can also use http://www.icloud.com to see what’s in your calendar.

Once you’re sure iCloud is working OK on your device, let’s try it out in an app of our own creation!

Enabling iCloud in your App


In this tutorial, we’ll be creating a simple app that manages a shared iCloud document called “dox”. The app will be universal and will be able to run on both iPhone and iPad, so we can see changes made on one device propagated to the other.

There are three steps to use iCloud in an app, so let’s try them out as we start this new project.

1. Create an iCloud-enabled App ID

To do this, visit the iOS Developer Center and log onto the iOS Provisioning Portal. Create a new App ID for your app similar to the below screenshot (but replace the bundle identifier with a unique name of your own).

Note: Be sure to end your App ID with “dox” in this tutorial, because that is what we will be naming the project. For example, you could enter com.yourname.dox.

Creating an App ID

After you create the App ID, you will see that Push Notifications and Game Center are automatically enabled, but iCloud requires you to manually enable it. Click the Configure button to continue.

Configuring an App ID to use iCloud

On the next screen, click the checkbox next to Enable for iCloud and click OK when the popup appears. If all works well, you will see a green icon next to the word Enabled. Then just click Done to finish.

Clicking Enable for iCloud in the iOS Provisioning Portal

2) Create a provisioning profile for that App ID

Still in the iOS Provisioning Portal, switch to the Provisioning tab, and click New Profile. Select the App ID you just created from the dropdown, and fill out the rest of the information, similar to the below screenshot:

Creating a Provisioning Profile

After creating the profile, refresh the page until it is ready for download, and then download it to your machine. Once it’s downloaded, double click it to bring it into Xcode, and verify that it is visible in Xcode’s Organizer:

Viewing Provisioning Profiles in Xcode Organizer

3) Configure your Xcode project for iCloud

Start up Xcode and create a new project with the iOS\Application\Single View Application template. Enter dox for the product name, enter the company identifier you used when creating your App ID, set the device family to Universal, and make sure Use Automatic Reference Counting is checked (but leave the other checkboxes unchecked):

Creating a new app in Xcode

Once you’ve finished creating the project, select your project in the Project Navigator and select the dox target. Select the Summary tab, and scroll way down to the Entitlements section.

Once you’re there, click the Enable Entitlements checkbox, and it will auto-populate the other fields based on your App ID, as shown below:

Setting entitlements in Xcode

This is what the fields here mean:

  • The Entitlements File points to a property list file which, much like the info.plist file, includes specifications about application entitlements.
  • The iCloud Key-Value Store represents the unique identifier which points to the key-value store in iCloud.
  • The iCloud Containers section represents “directories” in the cloud in which your application can read/write documents. Yes, you have read correctly, I said applications (plural), for a user’s container can be managed by more than one application. The only requirement is that applications have to be created by the same team (as set up in the iTunes Developer Center).
  • The Keychain Access Groups includes keys needed by applications which are sharing keychain data.

You don’t have to change anything from the defaults for this tutorial, so you’re ready to go! If you like you can edit the same settings by editing the file dox.entititlements which is included in your project.

Checking for iCloud Availability


When building an application which makes use of iCloud, the best thing to do is to check the availability of iCloud as soon as the application starts. Although iCloud is available on all iOS 5 devices, the user might not have configured it.

To avoid possible unattended behaviors or crashes, you should check if iCloud is available before using it. Let’s see how this works.

Open up AppDelegate.m, and add the following code at the bottom of application:didFinishLaunchingWithOptions (before the return YES):


NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(@"iCloud access at %@", ubiq);
// TODO: Load document... 
} else {
NSLog(@"No iCloud access");
}


Here we use a new method you haven’t seen yet called URLForUbiquityContainerIdentifier. This method allows you to pass in a container identifier (like you set up earlier in the iCloud Containers section) and it will return to you a URL to use to access files in iCloud storage.

You need to call this on startup for each container you want to access to give your app permission to access the URL. If you pass in nil to the method (like we do here), it automatically returns the first iCloud Container set up for the project. Since we only have one container, this makes it nice and easy.

Compile and run your project (on a device, because iCloud does not work on the simulator), and if all works well, you should see a message in your console like this:

iCloud access at file://localhost/private/var/mobile/Library/Mobile%20
Documents/KFCNEC27GU~com~razeware~dox/

Note that the URL this returns is actually a local URL on the system! This is because the iCloud daemon transfers files from the central servers to a local directory on the device on your behalf. Your application can then retrieve files from this directory, or send updated versions to this directory, and the iCloud daemon will synchronize everything for you.

This directory is outside of your app’s directory, but as mentioned above the act of calling URLForUbiquityContainerIdentifier gives your app permission to access this directory.

iCloud API Overview


Before we proceed further with the code, let’s take a few minutes to give an overview of the APIs we’ll be using to work with iCloud documents.

To store documents in iCloud, you can do things manually if you’d like, by moving files to/from the iCloud directory with new methods in NSFileManager and the new NSFilePresenter and NSFileCoordinator classes.

However doing this is fairly complex and unnecessary in most cases, because iOS 5 has introduced a new class to make working with iCloud documents much easier: UIDocument.

UIDocument acts as middleware between the file itself and the actual data (which in our case will be the text of a note). In your apps, you’d usually create a subclass of UIDocument and override a few methods on it that we’ll discuss below.

UIDocument implements the NSFilePresenter protocol for you and does its work in the background, so the application is not blocked when opening or saving files, and the user can continue working with it. Such a behavior is enabled by a double queue architecture.

The first queue, the main thread of the application, is the one where your code is executed. Here you can open, close and edit files. The second queue is on the background and it is managed by UIKit. For example let’s say we want to open a document, which has been already created on iCloud.

Imagine we have an instance of UIDocument and we want to open a file stored in iCloud. We’d send a message like the following:


[doc openWithCompletionHandler:^(BOOL success) {
// Code to run when the open has completed
}];


This triggers a ‘read’ message into the background queue. You can’t call this method directly, for it gets called when you execute openWithCompletionHandler. Such an operation might take some time (for example, the file might be very big, or not downloaded locally yet).

In the meantime we can do something else on the UI so the application is not blocked. Once the reading is done we are free to load the data returned by the read operation.

This is exactly where UIDocument comes in handy, because you can override the loadFromContents:ofType:error method to read the data into your UIDocument subclass. Here’s a simplified version what it will look like for our simple notes app:


- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
error:(NSError **)outError
{   
self.noteContent = [[NSString alloc]
initWithBytes:[contents bytes]
length:[contents length]
encoding:NSUTF8StringEncoding];          
return YES;   
}


This method is called by the background queue whenever the read operation has been completed.

The most important parameter here is contents, which is typically an NSData containing the actual data which you can use to create or update your model. You’d typically override this method to parse the NSData and pull out your document’s information, and store it in some instance variables in your UIDocument subclass, like shown here.

After loadFromContents:ofType:error completes, you’ll receive the callback you provided in the openWithCompletionHandler: block, as shown in the diagram below:

Order of operations when loading an UIDocument

To sum up, when you open a file you receive two callbacks: first in your UIDocument subclass when data has been read, and secondly when open operation is completely finished.

The write operation is pretty similar and it exploits the same double queue. The difference is that when opening a file we have to parse an NSData instance, but while writing we have to convert our document’s data to NSData and provide it to the background queue.

To save a document, you can either manually initiate the process by writing code, or via the autosaving feature implemented in UIDocument (more on this below).

If you want to save manually, you’d call a method like this:


[doc saveToURL:[doc fileURL]
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
// Code to run when the save has completed
}];


Just like when opening a file, there is a completion handler which is called when the writing procedure is done.

When asked to write the background queue asks for a snapshot of the contents of our UIDocument subclass. This is accomplished by overriding another method of UIDocument – contentsForType:error.

Here you should return an NSData instance which describes the current model to be saved. In our notes application, we’ll be returning an NSData representation of a string as follows:


- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
return [NSData dataWithBytes:[self.noteContent UTF8String]
length:[self.noteContent length]];

}


The rest is taken care of in the background queue, which manages the storing of data. Once done the code in the completion handler will be executed.

For sake of completeness we should mention that in both reading and writing, instead of NSData you can use NSFileWrapper. While NSData is meant to manage flat files NSFileWrapper can handle packages, that is a directory with files treated as a single file. We’ll cover using NSFileWrapper later in this tutorial.

As we mentioned earlier, the save operation can be called explicitly via code or triggered automatically. The UIDocument class implements a saveless model, where data is saved automatically at periodic intervals or when certain events occur. This way there is no need for the user to tap a ‘save’ button anymore, because the system manages that automatically, e.g. when you switch to another document.

Under the hood the UIKit background queue calls a method on UIDocument called hasUnsavedChanges which returns whether the document is “dirty” and needs to be saved. In case of positive response the document is automatically saved. There is no way to directly set the value for such a method but there are two ways to influence it.

The first way is to explicitly call the updateChangeCount: method. This notifies the background queue about changes. As an alternative you can use the undo manager which is built in the UIDocument class. Each instance of this class (or subclasses) has in fact a property undoManager. Whenever a change is registered via an undo or redo action the updateChangeCount: is called automatically.

It is important to remember that in either case the propagation of changes might not be immediate. By sending these messages we are only providing ‘hints’ to the background queue, which will start the update process when it’s appropriate according to the device and the type of connection.

Subclassing UIDocument


Now that you have a good overview of UIDocument, let’s create a subclass for our note application and see how it works!

Create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Note, and make it a subclass of UIDocument.

To keep things simple, our class will just have a single property to store the note as a string. To add this, replace the contents of Note.h with the following:


#import <UIKit/UIKit.h>

@interface Note : UIDocument

@property (strong) NSString * noteContent;

@end


As we have learned above we have two override points, one when we read and one when we write. Add the implementation of these by replacing Note.m with the following:


#import "Note.h"

@implementation Note

@synthesize noteContent;

// Called whenever the application reads data from the file system
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName
error:(NSError **)outError
{

if ([contents length] > 0) {
self.noteContent = [[NSString alloc]
initWithBytes:[contents bytes]
length:[contents length]
encoding:NSUTF8StringEncoding];       
} else {
// When the note is first created, assign some default content
self.noteContent = @"Empty";
}

return YES;   
}

// Called whenever the application (auto)saves the content of a note
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{

if ([self.noteContent length] == 0) {
self.noteContent = @"Empty";
}

return [NSData dataWithBytes:[self.noteContent UTF8String]
length:[self.noteContent length]];

}

@end


When we load a file we need a procedure to ‘transform’ the NSData contents returned by the background queue into a string. Conversely, when we save we have to encode our string into an NSData object. In both cases we do a quick check and assign a default value in case the string is empty. This happens the first time that the document is created.

Believe it or not, the code we need to model the document is already over! Now we can move to the code related to loading and updating.

Opening an iCloud File


First of all we should decide a file name for our document. For this tutorial, we’ll start by creating a single filename. Add the following #define at the top of AppDelegate.m:


#define kFILENAME @"mydocument.dox"


Next, let’s extend the application delegate to keep track of our document, and a metadata query to look up the document in iCloud. Modify AppDelegate.h to look like the following:


#import <UIKit/UIKit.h>
#import "Note.h"

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *viewController;
@property (strong) Note * doc;
@property (strong) NSMetadataQuery *query;

- (void)loadDocument;

@end


Then switch to AppDelegate.m and synthesize the new propeties:


@synthesize doc = _doc;
@synthesize query = _query;


We’ve already added code into application:didFinishLaunchingWithOptions to check for the availability of iCloud. If iCloud is available, we want to call the new method we’re about to write to load our document from iCloud, so add the following line of code right after where it says “TODO: Load document”:


[self loadDocument];


Next we’ll write the loadDocument method. Let’s put it together bit by bit so we can discuss all the code as we go.


- (void)loadDocument {

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;

}


Note that before we can load a document from iCloud, we first have to check what’s there. Remember that we can’t simply enumerate the local directory returned to us by URLForUbiquityContainerIdentifier, because there may be files in iCloud not yet pulled down locally.

If you ever worked with Spotlight on the Mac, you’ll be familiar with the class NSMetadataQuery. It is a class to represent results of a query related to the properties of an object, such as a file.

In building such a query you have the possibility to specify parameters and scope, i.e. what you are looking for and where. In the case of iCloud files the scope is always NSMetadataQueryUbiquitousDocumentsScope. You can have multiple scopes, so we have to build an array containing just one item.

So continue loadDocument as follows:


- (void)loadDocument {

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDocumentsScope]];

}


Now you can provide the parameters of the query. If you ever worked with CoreData or even arrays you probably know the approach. Basically, you build a predicate and set it as parameter of a query/search.

In our case we are looking for a file with a particular name, so the keyword is NSMetadataItemFSNameKey, where ‘FS’ stands for file system. Add the code to create and set the predicate next:


- (void)loadDocument {

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:
@"%K == %@", NSMetadataItemFSNameKey, kFILENAME];
[query setPredicate:pred];

}


You might not have seen the %K substitution before. It turns out predicates treat formatting characters a bit differently than you might be used to with NSString’s stringWithFormat. When you use %@ in predicates, it wraps the value you provide in quotes. You don’t want this for keypaths, so you use %K instead to avoid wrapping it in quotes. For more information, see the Predicate Format String Syntax in Apple’s documentation.

Now the query is ready to be run, but since it is an asynchronous process we need to set up an observer to catch a notification when it completes.

The specific notification we are interested in has a pretty long (but descriptive) name: NSMetadataQueryDidFinishGatheringNotification. This is posted when the query has finished gathering info from iCloud.

So here is the final implementation of our method:


- (void)loadDocument {

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:
@"%K == %@", NSMetadataItemFSNameKey, kFILENAME];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:query];

[query startQuery];

}


Now that this is in place, add the code for the method that will be called when the query completes:


- (void)queryDidFinishGathering:(NSNotification *)notification {

NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];

[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];

_query = nil;

[self loadData:query];

}


Note that once you run a query, if you don’t stop it it runs forever or until you quit the application. Especially in a cloud environment things can change often. It might happen that while you are processing the results of a query, due to live updates, the results change! So it is important to stop this process by calling disableUpdates and stopQuery. In particular the first prevents live updates and the second allows you to stop a process without deleting already collected results.

We then remove ourselves as an observer to ignore further notifications, and finally call a method to load the document, passing the NSMetadataQuery as a parameter.

Add the starter implementation of this method next (add this above queryDidFinishGathering):


- (void)loadData:(NSMetadataQuery *)query {

if ([query resultCount] == 1) {
NSMetadataItem *item = [query resultAtIndex:0];

}
}


As you can see here, a NSMetadataQuery wraps an array of NSMetadataItems which contain the results. In our case, we are working with just one file so we are just interested in the first element.

An NSMetadataItem is like a dictionary, storing keys and values. It has a set of predefined keys that you can use to look up information about each file:

  • NSMetadataItemURLKey
  • NSMetadataItemFSNameKey
  • NSMetadataItemDisplayNameKey
  • NSMetadataItemIsUbiquitousKey
  • NSMetadataUbiquitousItemHasUnresolvedConflictsKey
  • NSMetadataUbiquitousItemIsDownloadedKey
  • NSMetadataUbiquitousItemIsDownloadingKey
  • NSMetadataUbiquitousItemIsUploadedKey
  • NSMetadataUbiquitousItemIsUploadingKey
  • NSMetadataUbiquitousItemPercentDownloadedKey
  • NSMetadataUbiquitousItemPercentUploadedKey

In our case, we are interested in NSMetadataItemURLKey, which points to the URL that we need to build our Note instance. Continue the loadData method as follows:


- (void)loadData:(NSMetadataQuery *)query {

if ([query resultCount] == 1) {

NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
Note *doc = [[Note alloc] initWithFileURL:url];
self.doc = doc;

}
}


When you create a UIDocument (or a subclass of UIDocument like Note), you always have to use the initWithFileURL initializer and give it the URL of the document to open. We call that here, pasing in the URL of the located file, and store it away in an instance variable.

Now we are ready to open the note. As explained previously you can open a document with the openWithCompletionHandler method, so continue loadData as follows:


- (void)loadData:(NSMetadataQuery *)query {

if ([query resultCount] == 1) {

NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
Note *doc = [[Note alloc] initWithFileURL:url];
self.doc = doc;
[self.doc openWithCompletionHandler:^(BOOL success) {
if (success) {               
NSLog(@"iCloud document opened");                   
} else {               
NSLog(@"failed opening document from iCloud");               
}
}];

}
}


You can run the app now, and it seems to work… except it never prints out either of the above messages! This is because there is currently no document in our container in iCloud, so the search isn’t finding anything (and the result count is 0).

Since the only way to add a document on the iCloud is via an app, we need to write some code to create a doc. We will append this to the loadData method that we defined a few seconds ago. When the query returns zero results, we should:

  • Retrieve the local iCloud directory
  • Initialize an instance of document in that directory
  • Call the saveToURL method
  • When the save is successful we can call openWithCompletionHandler.

So add an else case to the if statement in loadData as follows:


else {

NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:
@"Documents"] URLByAppendingPathComponent:kFILENAME];

Note *doc = [[Note alloc] initWithFileURL:ubiquitousPackage];
self.doc = doc;

[doc saveToURL:[doc fileURL]
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {           
if (success) {
[doc openWithCompletionHandler:^(BOOL success) {               
NSLog(@"new document opened from iCloud");               
}];               
}
}];
}


Compile and run your app, and you should see the “new document” message arrive the first time you run it, and “iCloud document opened” in subsequent runs.

You can even try this on a second device (I recommend temporarily commenting out the else case first though to avoid creating two documents due to timing issues), and you should see the “iCloud document opened” message show up on the second device (because the document already exists on iCloud now!)

Now our application is almost ready. The iCloud part is over, and we just need to set up the UI – which we’ll continue in the next part of the series!

Where To Go Form Here?


At this point, you should have some basic experience working with iCloud. But stay tuned for the next part to the tutorial, where we’ll add a UI onto the app, and cover how extend our app to work with multiple documents!

This tutorial is a sample chapter from our new book iOS 5 By Tutorials. If you like this tutorial, you might want to check out the book, because the final version will include an entire additional chapter on iCloud not posted here!

If you have any comments or questions on this tutorial or on iCloud in general, please join the forum discussion below!

This is a post by iOS Tutorial Team member Cesare Rocchi, a UX designer and developer specializing in web and mobile applications. He runs Studio Magnolia, an interactive studio that creates compelling web and mobile applications.

Beginning iCloud in iOS 5 Tutorial Part 1 is a post from: Ray Wenderlich