Como um iniciante, estou lutando com o iCloud. Existem alguns exemplos, mas eles geralmente são bastante detalhados (no fórum de desenvolvedores há um para iCloud e CoreData que é enorme). Os documentos da apple estão OK, mas ainda não consigo ver o quadro geral. Então, por favor, tenha paciência comigo, algumas dessas perguntas são bastante fundamentais, mas possivelmente fáceis de responder.
Contexto: Tenho um aplicativo iCloud muito simples em execução (código de exemplo completo abaixo). Existe apenas um UITextView mostrado ao usuário e sua entrada é salva em um arquivo chamado text.txt.
O arquivo txt é enviado para a nuvem e disponibilizado para todos os dispositivos. Funciona perfeitamente, mas:
Principal problema: E quanto aos usuários que não usam o iCloud?
Quando eu inicio meu aplicativo (veja o código abaixo), eu verifico se o usuário tem o iCloud habilitado. Se o iCloud estiver ativado, está tudo bem. O aplicativo segue em frente e procura por text.txt na nuvem. Se encontrado, ele irá carregá-lo e exibi-lo para o usuário. Se text.txt não for encontrado na nuvem, ele simplesmente criará um novo text.txt e o exibirá ao usuário.
Se o usuário não tiver o iCloud habilitado, nada acontecerá. Como possibilitarei que usuários não-iCloud ainda possam trabalhar com meu aplicativo de texto? Ou simplesmente os ignoro? Eu precisaria escrever funções separadas para usuários que não são do iCloud? Ou seja, funções nas quais eu simplesmente carrego um text.txt da pasta de documentos?
Trate os arquivos no iCloud da mesma maneira que trata todos os outros arquivos na caixa de proteção do aplicativo.
No entanto, no meu caso, não há mais sandbox de aplicativo 'normal'. Está na nuvem. Ou sempre carrego primeiro meu text.txt do disco e depois verifico com o iCloud se há algo mais atualizado?
Problema relacionado: Estrutura de arquivos - Sandbox vs. Nuvem
Talvez meu principal problema seja um mal-entendido fundamental sobre como o iCloud deve funcionar. Quando eu crio uma nova instância de um UIDocument, terei que substituir dois métodos. Primeiro, - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
para obter arquivos da nuvem e, em seguida, -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
para obter arquivos para a nuvem.
Devo incorporar funções separadas que também salvam uma cópia local do text.txt em minha sandbox? Isso funcionará para usuários que não são do iCloud? Pelo que entendi, o iCloud salvará uma cópia local do text.txt automaticamente. Portanto, não deve haver nenhuma necessidade de eu salvar nada na 'velha' caixa de areia do meu aplicativo (ou seja, como costumava ser nos dias anteriores ao iCloud). No momento, minha sandbox está totalmente vazia, mas não sei se isso está correto. Devo manter outra cópia do text.txt lá? Isso parece bagunçar minha estrutura de dados ... já que há um text.txt na nuvem, um na sandbox do iCloud no meu dispositivo (que funcionará mesmo se eu estiver offline) e um terceiro na boa e velha sandbox do meu app ...
MEU CÓDIGO: Um código de exemplo simples do iCloud
Isso é vagamente baseado em um exemplo que encontrei no fórum de desenvolvedores e no vídeo da sessão WWDC. Eu reduzi ao mínimo. Não tenho certeza se minha estrutura MVC é boa. O modelo está no AppDelegate, o que não é o ideal. Quaisquer sugestões para torná-lo melhor são bem-vindas.
EDIT: Tentei extrair a pergunta principal e postei-a [aqui]. 4
VISÃO GERAL:
A parte mais importante que carrega o text.txt da nuvem:
// AppDelegate.h
// iCloudText
#import <UIKit/UIKit.h>
@class ViewController;
@class MyTextDocument;
@interface AppDelegate : UIResponder <UIApplicationDelegate> {
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
O UIDocument
// MyTextDocument.h
// iCloudText
#import <Foundation/Foundation.h>
#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] > 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
O VIEWCONTROLLER
//
// ViewController.h
// iCloudText
#import <UIKit/UIKit.h>
@class MyTextDocument;
@interface ViewController : UIViewController <UITextViewDelegate> {
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];
}