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];
}
fuente
Respuestas:
Acabo de volver a leer los documentos y parece que mi enfoque general es incorrecto. Primero debería crear el archivo en la caja de arena y luego moverlo a la nube. En otras palabras, Apple parece sugerir que debería tener tres versiones del mismo archivo en todo momento: una en el directorio de mi aplicación, una en el directorio demonio de iCloud de mi dispositivo (que también es accesible si está desconectado) y una en la nube:
Sin embargo, si profundiza un poco más en los documentos relacionados con setUbiquitous, encontrará:
Entonces, esto parece significar que un archivo / directorio se elimina del entorno de pruebas local y se mueve a la nube.
fuente
He estado usando tu ejemplo y me gusta porque me ayuda a comprender los conceptos básicos de iCloud. Ahora estoy discutiendo con su pregunta para mi propia aplicación, que tiene que admitir a los usuarios existentes de la aplicación con contenido almacenado localmente que pueden o no usar iCloud creando estos casos, por lo que puedo decir:
Casos:
Si alguien elimina iCloud, ¿las llamadas a la URL ubicua no devolverían nada? Si ese es el caso, ¿cómo puedo migrar los documentos al almacenamiento local? Crearé una preferencia de usuario por ahora, pero parece una pequeña solución.
Siento que me estoy perdiendo algo obvio aquí, así que si alguien puede verlo, por favor intervenga.
fuente
Si desea que los usuarios puedan compartir texto entre dispositivos que son anteriores a iOS 5.0, tendrá que hacer lo que todos tenían que hacer antes de iCloud y mover la información a su propio servidor.
Todo lo que realmente necesita es un servidor en algún lugar que le permita a su aplicación guardar sus archivos de texto y asociarlos con una cuenta de usuario.
Necesitará que los usuarios creen una cuenta y deberá administrar el proceso usted mismo, de mover nueva información en un dispositivo a su propia 'nube'.
Los usuarios se registrarán con la misma cuenta en otros dispositivos y deberá encargarse de detectar cuándo otro dispositivo ha movido datos a su propia nube y actualizar el dispositivo actual con la nueva información.
Obviamente, para los dispositivos iOS 5.0, probablemente querrá detectar archivos modificados para dispositivos anteriores a iOS 5.0 en su propia nube, y también poder hablar con iCloud.
fuente
No parece que esté luchando con un problema de iCloud / notICloud tanto como con un problema de iOS5 / notIOS5.
Si su destino de implementación es iOS5, simplemente use siempre la estructura UIDocument. Si es ubicuo, entonces su NSMetaDataQuery lo encontrará en la nube; si no, lo encontrará en el dispositivo.
Si, por otro lado, desea proporcionar acceso anterior a la 5.0 a su aplicación, entonces deberá verificar condicionalmente si el iOS en ejecución es 5.0 o superior. Si es así, utilice UIDocument; si no, lea / escriba los datos de la manera anterior.
Mi enfoque fue escribir un método saveData condicional que verifique iOS5. Si existe, actualizo el recuento de cambios (o uso un administrador de deshacer). En su caso, textViewDidChange llamaría a este método. Si no es así, se guarda en el disco de la forma anterior. Al cargar, ocurre lo contrario.
fuente
Está desconcertado por "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". Esto es válido para algo como Keynote y Numbers, donde guardas un montón de archivos, y si tienes iCloud, comienzan a sincronizarse mágicamente.
Sin embargo, está creando algo que depende de la funcionalidad similar a iCloud. No puede aferrarse a esa declaración porque su aplicación depende de que iCloud esté presente para que cualquier cosa funcione de la manera que debe. Tendrá que cerrar su aplicación y simplemente decir "por favor configure iCloud para que esto funcione" o duplicar la funcionalidad similar a iCloud (la suya o la de otra persona) que siempre puede usar, independientemente.
fuente