Come principiante, sto lottando con iCloud. Ci sono alcuni esempi, ma di solito sono abbastanza dettagliati (sul forum degli sviluppatori ce n'è uno per iCloud e CoreData che è enorme). I documenti Apple sono OK, ma non riesco ancora a vedere il quadro generale. Quindi per favore abbiate pazienza, alcune di queste domande sono abbastanza fondamentali, ma forse è facile rispondere.
Contesto: ho un'app iCloud molto semplice in esecuzione (codice di esempio completo di seguito). C'è un solo UITextView mostrato all'utente e il suo input viene salvato in un file chiamato text.txt.
Il file txt viene inviato al cloud e reso disponibile a tutti i dispositivi. Funziona perfettamente, ma:
Problema principale: che dire degli utenti che non utilizzano iCloud?
Quando avvio la mia app (vedi codice sotto), controllo se l'utente ha iCloud abilitato. Se iCloud è abilitato, va tutto bene. L'app va avanti e cerca text.txt nel cloud. Se trovato, lo caricherà e lo mostrerà all'utente. Se text.txt non viene trovato nel cloud, creerà semplicemente un nuovo text.txt e lo mostrerà all'utente.
Se l'utente non ha iCloud abilitato, non accadrà nulla. Come posso rendere possibile che gli utenti non iCloud possano ancora lavorare con la mia app di testo? O semplicemente li ignoro? Avrei bisogno di scrivere funzioni separate per utenti non iCloud? Cioè funzioni in cui carico semplicemente un text.txt dalla cartella dei documenti?
Tratta i file in iCloud nello stesso modo in cui tratti tutti gli altri file nella sandbox dell'app.
Tuttavia, nel mio caso non esiste più una sandbox "normale" dell'app. È nel cloud. Oppure carico sempre il mio text.txt prima dal disco e poi controllo con iCloud se c'è qualcosa di più aggiornato?
Problema correlato: struttura dei file - Sandbox vs. Cloud
Forse il mio problema principale è un malinteso fondamentale su come dovrebbe funzionare iCloud. Quando creo una nuova istanza di un documento UID, dovrò sovrascrivere due metodi. Prima - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
per ottenere i file dal cloud e poi -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
per ottenere i file nel cloud.
Devo incorporare funzioni separate che salveranno anche una copia locale di text.txt nella mia sandbox? Funzionerà per gli utenti non iCloud? Da quanto ho capito iCloud, salverà automaticamente una copia locale di text.txt. Quindi non dovrebbe esserci alcuna necessità per me di salvare nulla nella "vecchia" sandbox della mia app (cioè come era ai vecchi tempi pre-iCloud). In questo momento, la mia sandbox è completamente vuota, ma non so se sia corretto. Devo tenere un'altra copia di text.txt lì dentro? Mi sembra di ingombrare la mia struttura dei dati ... poiché c'è un text.txt nel cloud, uno nella sandbox di iCloud sul mio dispositivo (che funzionerà anche se sono offline) e un terzo nel buon vecchio sandbox di la mia app ...
IL MIO CODICE: un semplice codice di esempio iCloud
Questo è vagamente basato su un esempio che ho trovato nel forum degli sviluppatori e nel video della sessione WWDC. L'ho spogliato al minimo indispensabile. Non sono sicuro che la mia struttura MVC sia buona. Il modello è in AppDelegate che non è l'ideale. Eventuali suggerimenti per migliorarlo sono i benvenuti.
EDIT: ho provato a estrarre la domanda principale e l'ho pubblicata [qui]. 4
PANORAMICA:
Il bit più importante che carica il text.txt dal cloud:
// 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
Il documento UID
// 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
IL 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];
}