Note that there are some explanatory texts on larger screens.

plurals
  1. POiCloud basics and code sample
    text
    copied!<p>As a beginner, I'm struggling with iCloud. There are some samples, but they are usually quite detailed (on the developer forum there is one for iCloud and CoreData which is massive). The <a href="http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/iCloud/iCloud.html#//apple_ref/doc/uid/TP40007072-CH5-SW10" rel="nofollow noreferrer">apple docs</a> are OK, but I still can't see the big picture. So please bear with me, some of these questions are quite fundamental, but possibly easy to answer.</p> <p><strong>Context:</strong> I have a very simple iCloud app running (full sample code below). There is only one UITextView shown to the user and his/her input is saved in a file called text.txt.</p> <p><img src="https://i.stack.imgur.com/I2m4l.png" alt="enter image description here"></p> <p>The txt file is pushed to the cloud and made available to all devices. Works perfectly, but:</p> <p><strong>Main problem: What about users who do not use iCloud?</strong></p> <p>When I launch my app (see code below), I check if the user has iCloud enabled. If iCloud is enabled, everything is fine. The app goes ahead and looks for text.txt in the cloud. If found, it will load it and display it to the user. If text.txt is not found in the cloud, it will simply create a new text.txt and will display that to the user. </p> <p>If the user does not have iCloud enabled, nothing will happen. How will I make it possible that non-iCloud users can still work with my text app? Or do I simply ignore them? Would I need to write separate functions for non-iCloud users? I.e. functions in which I simply load a text.txt from the documents folder?</p> <p><a href="http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/iCloud/iCloud.html#//apple_ref/doc/uid/TP40007072-CH5-SW10" rel="nofollow noreferrer">Apple writes</a>: </p> <blockquote> <p>Treat files in iCloud the same way you treat all other files in your app sandbox.</p> </blockquote> <p>However, in my case there is no 'normal' app sandbox anymore. It's in the cloud. Or do I always load my text.txt from disk first and then check with iCloud if there is something more up-to-date?</p> <p><strong>Related problem: File structure - Sandbox vs. Cloud</strong></p> <p>Perhaps my main problem is a fundamental misunderstanding of how iCloud is supposed to work. When I create a new instance of an UIDocument, I'll have to overwrite two methods. First <code>- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError</code> to get files from the cloud and then <code>-(id)contentsForType:(NSString *)typeName error:(NSError **)outError</code> to get files into the cloud.</p> <p>Do I have to incorporate separate functions which will also save a local copy of text.txt into my sandbox? Will this work for non-iCloud users? As I understand iCloud, it will save a local copy of text.txt automatically. So there shouldn't be any need for me save anything into the 'old' sandbox of my app (i.e. as it used to be in the old, pre-iCloud days). Right now, my sandbox is totally empty, but I don't know if this is correct. Should I keep another copy of text.txt in there? This feels like cluttering my data structure... as there is one text.txt in the cloud, one in the iCloud sandbox on my device (which will work even if I am offline), and a third one in the good old sandbox of my app...</p> <hr> <p>MY CODE: A simple iCloud sample code</p> <p>This is loosely based on an example I found in the developer forum and on the WWDC session video. I stripped it down to the bare minimum. I'm not sure that my MVC structure is any good. The model is in the AppDelegate which isn't ideal. Any suggestions to make it better are welcome.</p> <hr> <p>EDIT: I tried to extract the main question and posted it [here].<a href="https://stackoverflow.com/questions/7798555/icloud-should-i-stop-storing-data-in-the-documents-folder-and-put-everything-in">4</a></p> <hr> <p>OVERVIEW:</p> <p><img src="https://i.stack.imgur.com/hKGYY.png" alt="Overview"></p> <p><strong>The most important bit which loads the text.txt from the cloud:</strong></p> <pre><code>// AppDelegate.h // iCloudText #import &lt;UIKit/UIKit.h&gt; @class ViewController; @class MyTextDocument; @interface AppDelegate : UIResponder &lt;UIApplicationDelegate&gt; { NSMetadataQuery *_query; } @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ViewController *viewController; @property (strong, nonatomic) MyTextDocument *document; @end // AppDelegate.m // iCloudText #import "AppDelegate.h" #import "MyTextDocument.h" #import "ViewController.h" @implementation AppDelegate @synthesize window = _window; @synthesize viewController = _viewController; @synthesize document = _document; - (void)dealloc { [_window release]; [_viewController release]; [super dealloc]; } - (void)loadData:(NSMetadataQuery *)query { // (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document if ([query resultCount] == 1) { // found the file in iCloud NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url]; //_document = doc; doc.delegate = self.viewController; self.viewController.document = doc; [doc openWithCompletionHandler:^(BOOL success) { if (success) { NSLog(@"AppDelegate: existing document opened from iCloud"); } else { NSLog(@"AppDelegate: existing document failed to open from iCloud"); } }]; } else { // Nothing in iCloud: create a container for file and give it URL NSLog(@"AppDelegate: ocument not found in iCloud."); NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:@"text.txt"]; MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage]; //_document = doc; doc.delegate = self.viewController; self.viewController.document = doc; [doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { NSLog(@"AppDelegate: new document save to iCloud"); [doc openWithCompletionHandler:^(BOOL success) { NSLog(@"AppDelegate: new document opened from iCloud"); }]; }]; } } - (void)queryDidFinishGathering:(NSNotification *)notification { // (3) if Query is finished, this will send the result (i.e. either it found our text.dat or it didn't) to the next function NSMetadataQuery *query = [notification object]; [query disableUpdates]; [query stopQuery]; [self loadData:query]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query]; _query = nil; // we're done with it } -(void)loadDocument { // (2) iCloud query: Looks if there exists a file called text.txt in the cloud NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; //SCOPE [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]]; //PREDICATE NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, @"text.txt"]; [query setPredicate:pred]; //FINISHED? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query]; [query startQuery]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"AppDelegate: app did finish launching"); self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // Override point for customization after application launch. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease]; } else { self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease]; } self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; // (1) iCloud: init NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; if (ubiq) { NSLog(@"AppDelegate: iCloud access!"); [self loadDocument]; } else { NSLog(@"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings"); } return YES; } @end </code></pre> <hr> <p><strong>The UIDocument</strong></p> <pre><code>// MyTextDocument.h // iCloudText #import &lt;Foundation/Foundation.h&gt; #import "ViewController.h" @interface MyTextDocument : UIDocument { NSString *documentText; id delegate; } @property (nonatomic, retain) NSString *documentText; @property (nonatomic, assign) id delegate; @end // MyTextDocument.m // iCloudText #import "MyTextDocument.h" #import "ViewController.h" @implementation MyTextDocument @synthesize documentText = _text; @synthesize delegate = _delegate; // ** READING ** - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { NSLog(@"UIDocument: loadFromContents: state = %d, typeName=%@", self.documentState, typeName); if ([contents length] &gt; 0) { self.documentText = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding]; } else { self.documentText = @""; } NSLog(@"UIDocument: Loaded the following text from the cloud: %@", self.documentText); // update textView in delegate... if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) { [_delegate noteDocumentContentsUpdated:self]; } return YES; } // ** WRITING ** -(id)contentsForType:(NSString *)typeName error:(NSError **)outError { if ([self.documentText length] == 0) { self.documentText = @"New Note"; } NSLog(@"UIDocument: Will save the following text in the cloud: %@", self.documentText); return [NSData dataWithBytes:[self.documentText UTF8String] length:[self.documentText length]]; } @end </code></pre> <hr> <p><strong>THE VIEWCONTROLLER</strong></p> <pre><code>// // ViewController.h // iCloudText #import &lt;UIKit/UIKit.h&gt; @class MyTextDocument; @interface ViewController : UIViewController &lt;UITextViewDelegate&gt; { IBOutlet UITextView *textView; } @property (nonatomic, retain) UITextView *textView; @property (strong, nonatomic) MyTextDocument *document; -(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument; @end // ViewController.m // iCloudText #import "ViewController.h" #import "MyTextDocument.h" @implementation ViewController @synthesize textView = _textView; @synthesize document = _document; -(IBAction)dismissKeyboard:(id)sender { [_textView resignFirstResponder]; } -(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument { NSLog(@"VC: noteDocumentsUpdated"); _textView.text = noteDocument.documentText; } -(void)textViewDidChange:(UITextView *)theTextView { NSLog(@"VC: textViewDidChange"); _document.documentText = theTextView.text; [_document updateChangeCount:UIDocumentChangeDone]; } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload