Como principiante, estoy luchando con iCloud. Hay algunas muestras, pero generalmente son bastante detalladas (en el foro de desarrolladores hay una para iCloud y CoreData que es masiva). Los documentos de Apple están bien, pero todavía no puedo ver el panorama general. Así que por favor tengan paciencia conmigo, algunas de estas preguntas son bastante fundamentales, pero posiblemente fáciles de responder.
Contexto: Tengo una aplicación iCloud muy simple en ejecución (código de muestra completo a continuación). Solo se muestra un UITextView al usuario y su entrada se guarda en un archivo llamado text.txt.
El archivo txt se envía a la nube y está disponible para todos los dispositivos. Funciona perfectamente, pero:
Problema principal: ¿Qué pasa con los usuarios que no usan iCloud?
Cuando ejecuto mi aplicación (ver código a continuación), verifico si el usuario tiene iCloud habilitado. Si iCloud está habilitado, todo está bien. La aplicación sigue adelante y busca text.txt en la nube. Si lo encuentra, lo cargará y se lo mostrará al usuario. Si no se encuentra text.txt en la nube, simplemente creará un nuevo text.txt y se lo mostrará al usuario.
Si el usuario no tiene iCloud habilitado, no pasará nada. ¿Cómo haré posible que los usuarios que no son de iCloud puedan seguir trabajando con mi aplicación de texto? ¿O simplemente los ignoro? ¿Necesitaría escribir funciones separadas para usuarios que no son de iCloud? Es decir, funciones en las que simplemente cargo un text.txt desde la carpeta de documentos.
Trate los archivos en iCloud de la misma manera que trata todos los demás archivos en la zona de pruebas de su aplicación.
Sin embargo, en mi caso ya no existe una zona de pruebas de aplicaciones "normal". Está en la nube. ¿O siempre cargo primero mi text.txt desde el disco y luego verifico con iCloud si hay algo más actualizado?
Problema relacionado: Estructura de archivos - Sandbox vs.Nube
Quizás mi principal problema es un malentendido fundamental de cómo se supone que funciona iCloud. Cuando creo una nueva instancia de un UIDocument, tendré que sobrescribir dos métodos. Primero - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
para obtener archivos de la nube y luego -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
para llevarlos a la nube.
¿Tengo que incorporar funciones separadas que también guardarán una copia local de text.txt en mi caja de arena? ¿Funcionará esto para usuarios que no son de iCloud? Según tengo entendido, iCloud guardará una copia local de text.txt automáticamente. Por lo tanto, no debería ser necesario que guarde nada en la caja de arena 'antigua' de mi aplicación (es decir, como solía ser en los días anteriores a iCloud). En este momento, mi caja de arena está totalmente vacía, pero no sé si esto es correcto. ¿Debo guardar otra copia de text.txt allí? Esto se siente como saturar mi estructura de datos ... ya que hay un text.txt en la nube, uno en la caja de arena de iCloud en mi dispositivo (que funcionará incluso si estoy desconectado) y una tercera en la vieja caja de arena de mi aplicación ...
MI CÓDIGO: un código de muestra simple de iCloud
Esto está vagamente basado en un ejemplo que encontré en el foro de desarrolladores y en el video de la sesión de la WWDC. Lo reduje al mínimo. No estoy seguro de que mi estructura MVC sea buena. El modelo está en AppDelegate, lo que no es ideal. Cualquier sugerencia para mejorarlo es bienvenida.
EDITAR: Traté de extraer la pregunta principal y la publiqué [aquí]. 4
VISIÓN DE CONJUNTO:
El bit más importante que carga el text.txt desde la nube:
// 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
El 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
EL CONTROLADOR DE VISTA
//
// 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];
}