Nozioni di base su iCloud ed esempio di codice [chiuso]


86

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.

inserisci qui la descrizione dell'immagine

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?

Apple scrive :

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 **)outErrorper ottenere i file dal cloud e poi -(id)contentsForType:(NSString *)typeName error:(NSError **)outErrorper 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:

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];

}

4
Suggerirei davvero di suddividere questo in un paio di domande. Vedo alcune domande diverse sepolte qui, ed è difficile individuarle nel muro di testo che hai qui. Tornerei a questa domanda chiedendo solo cosa fare per le persone che non hanno iCloud abilitato e suddividerei gli altri (con solo le parti rilevanti del tuo codice di esempio) in domande separate. Sono buone domande, ma penso che dovrebbero essere separate.
Brad Larson

@BradLarson Grazie per il tuo commento. Mi dispiace se le domande sono un po 'confuse, ma penso che la domanda principale (come ho cercato di indicare) sia il problema della sandbox dell'app rispetto alla sandbox di iCloud. Ho fornito il codice completo (che è il più breve esempio di codice iCloud, btw) perché pensavo che l'INTERO contesto fosse vitale per sapere cosa sta succedendo ... Ma potrei semplicemente aprire un'altra domanda e collegarlo a questa domanda a ottenere il quadro più ampio.
n.evermind

@BradLarson OK, ho aperto una nuova domanda qui: stackoverflow.com/questions/7798555/...
n.evermind

Per coloro che stanno
Duncan Groenewald

Non dovrebbe essere chiuso questo è in realtà uno dei post più costruttivi che ho visto su iCloud ..
Andrew Smith

Risposte:


22

Ho appena riletto i documenti e sembra che il mio approccio generale sia sbagliato. Dovrei prima creare il file nella sandbox e poi spostarlo nel cloud. In altre parole, Apple sembra suggerire che dovrei avere sempre tre versioni dello stesso file: una nella directory della mia app, una nella directory dei demoni di iCloud del mio dispositivo (che è accessibile anche se offline) e una in la nuvola:

Le app utilizzano le stesse tecnologie per gestire file e directory in iCloud che utilizzano per file e directory locali. I file e le directory in iCloud sono ancora solo file e directory. Puoi aprirli, crearli, spostarli, copiarli, leggere e scrivere da essi, eliminarli o qualsiasi altra operazione che potresti voler fare. L'unica differenza tra i file e le directory locali e i file e le directory di iCloud è l'URL che utilizzi per accedervi. Invece di essere gli URL relativi alla sandbox della tua app, gli URL per i file e le directory iCloud sono relativi alla directory del contenitore iCloud corrispondente.

Per spostare un file o una directory su iCloud:

Crea il file o la directory localmente nella sandbox dell'app. Durante l'utilizzo, il file o la directory devono essere gestiti da un presentatore di file, come un oggetto UIDocument.

Utilizza il metodo URLForUbiquityContainerIdentifier: per recuperare un URL per la directory del contenitore iCloud in cui desideri memorizzare l'elemento. Utilizza l'URL della directory del contenitore per creare un nuovo URL che specifichi la posizione dell'elemento in iCloud. Chiama il metodo setUbiquitous: itemAtURL: destinationURL: error: di NSFileManager per spostare l'elemento su iCloud. Non chiamare mai questo metodo dal thread principale della tua app; ciò potrebbe bloccare il thread principale per un periodo di tempo prolungato o causare un deadlock con uno dei presentatori di file della tua app. Quando sposti un file o una directory su iCloud, il sistema copia quell'elemento dalla sandbox dell'app e in una directory locale privata in modo che possa essere monitorato dal demone iCloud. Anche se il file non è più nella tua sandbox, la tua app ha ancora pieno accesso ad esso. Sebbene una copia del file rimanga locale sul dispositivo corrente, il file viene inviato anche a iCloud in modo che possa essere distribuito ad altri dispositivi. Il demone iCloud gestisce tutto il lavoro per assicurarsi che le copie locali siano le stesse. Quindi, dal punto di vista della tua app, il file è solo in iCloud.

Tutte le modifiche apportate a un file o una directory in iCloud devono essere effettuate utilizzando un oggetto coordinatore di file. Queste modifiche includono lo spostamento, l'eliminazione, la copia o la ridenominazione dell'elemento. Il coordinatore del file assicura che il demone iCloud non cambi il file o la directory allo stesso tempo e garantisce che le altre parti interessate siano informate delle modifiche apportate.

Tuttavia, se approfondisci un po 'la documentazione relativa a setUbiquitous, troverai:

Usa questo metodo per spostare un file dalla sua posizione corrente su iCloud. Per i file che si trovano nella sandbox di un'applicazione, ciò comporta la rimozione fisica del file dalla directory sandbox . (Il sistema estende i privilegi sandbox della tua applicazione per consentirle di accedere ai file che sposta su iCloud.) Puoi anche utilizzare questo metodo per spostare i file fuori da iCloud e di nuovo in una directory locale.

Quindi questo sembra significare che un file / directory viene eliminato dalla sandbox locale e spostato nel cloud.


1
il collegamento URL è interrotto ...
ngb

5

Ho usato il tuo esempio e mi piace per avermi aiutato a comprendere le basi di iCloud. Ora sto discutendo con la tua domanda per la mia app che deve supportare gli utenti esistenti dell'app con contenuti archiviati localmente che potrebbero o meno utilizzare iCloud creando questi casi per quanto ne so:

Casi:

  1. Nuovo utente
    • has icloud - crea documenti in icloud
    • no icloud: crea documenti localmente
  2. Utente esistente
    • ha icloud
      • appena aggiunto: migra i documenti locali su icloud
      • non solo aggiunto: apri / salva i documenti su icloud
    • no icloud
      • appena rimosso: migra i documenti precedenti di icloud in locale
      • non solo rimosso: apri / salva i documenti in locale

Se qualcuno rimuove iCloud, le chiamate all'URL onnipresente non tornerebbero nulle? In tal caso, come faccio a migrare nuovamente i documenti nell'archivio locale? Creerò una pref utente per ora, ma sembra un po 'una soluzione alternativa.

Mi sento come se mi mancasse qualcosa di ovvio qui, quindi se qualcuno può vederlo, per favore intervieni.


Dovrei aggiungere che mi chiedo se esiste una classe che gestisce questi casi, quindi la uso e non devo preoccuparmi di dove salvarla.
earnshavian

Dai un'occhiata a developer.apple.com/library/ios/#documentation/DataManagement/… che fornisce un codice di esempio per determinare se qualcosa deve essere messo nella sandbox locale o nel cloud.
n.evermind

Grazie per quello. Avevo visto quel documento ma in precedenza nella mia missione iCloud, quindi avevo dimenticato il codice che offre. Proverò ad adattare il tuo campione per supportare locale e remoto. Non mi è ancora chiaro come gestiamo l'utente che disabilita iCloud poiché perdiamo l'onnipresente URL, ma farò un tentativo e condividerò un aggiornamento.
earnshavian

1
Quindi, in un certo senso, è un po 'stupido dover utilizzare URL per il cloud e PATH per la sandbox locale. Sarebbe bello se iCloud potesse gestire tutto per noi ... ma in questo modo, abbiamo fondamentalmente bisogno di codificare due metodi diversi per ogni file che apriamo.
n.evermind

Ho appena riletto il tuo post. Ora sto salvando la preferenza dell'utente (cioè l'utente vuole / non vuole usare iCloud) in NSUserDefaults. Questo è ciò che suggerisce anche Apple. Controllo sempre se iCloud è accessibile. Se non è accessibile, dico agli utenti di attivarlo, ma solo se non hanno esplicitamente detto all'app che non desiderano utilizzarlo. Altrimenti diventa fastidioso per coloro che non desiderano utilizzare iCloud. Una volta determinato se iCloud è abilitato, seguirò l'onnipresente percorso URL e userò UIDocument OPPURE aprirò semplicemente i file dalla sandbox come ai bei vecchi tempi.
n.evermind

4

Se desideri che gli utenti siano in grado di condividere testo tra dispositivi pre-iOS 5.0, dovrai fare ciò che tutti dovevano fare prima di iCloud e spostare le informazioni sul tuo server.

Tutto ciò di cui hai veramente bisogno è un server da qualche parte che consenta alla tua app di salvare i suoi file di testo e associarli a un account utente.

Avrai bisogno che gli utenti creino un account e dovrai gestire tu stesso il processo di spostamento delle nuove informazioni su un dispositivo nel tuo "cloud".

Gli utenti si registreranno con lo stesso account su altri dispositivi e dovrai occuparti di rilevare quando un altro dispositivo ha spostato i dati sul tuo cloud e aggiornare il dispositivo corrente con le nuove informazioni.

Ovviamente, per i dispositivi iOS 5.0, probabilmente vorrai rilevare i file modificati per i dispositivi pre-iOS 5.0 nel tuo cloud e anche essere in grado di parlare con iCloud.


Grazie. Quindi, in altre parole, se non voglio supportare i dispositivi pre-iOS 5, vado semplicemente con UIDocument e dimentico il contenuto della directory dei documenti nella sandbox della mia app.
n.evermind

Praticamente, anche se per quanto posso dire, avrai ancora un documento nella sandbox che UIDocument ti aiuterà a mediare con iCloud per te, ma ti verrà detto quando potrai accedervi ... Sto ancora ricevendo fare i conti con questa roba da solo!
Jonathan Watmough

3

Non sembra che tu stia lottando con un problema di iCloud / notICloud tanto quanto un problema di iOS5 / notIOS5.

Se il tuo obiettivo di distribuzione è iOS5, usa semplicemente sempre la struttura UIDocument. Se è onnipresente, il tuo NSMetaDataQuery lo troverà nel cloud; in caso contrario lo troverà sul dispositivo.

Se, d'altra parte, desideri fornire l'accesso precedente alla 5.0 alla tua app, dovrai controllare in modo condizionale se l'iOS in esecuzione è 5.0 o superiore. In tal caso, utilizzare UIDocument; in caso contrario, leggere / scrivere i dati alla vecchia maniera.

Il mio approccio consisteva nello scrivere un metodo saveData condizionale che verifichi la presenza di iOS5. Se esiste aggiorno il conteggio delle modifiche (o utilizzo un gestore di annullamenti). Nel tuo caso, textViewDidChange chiamerebbe questo metodo. In caso contrario, salva su disco alla vecchia maniera. Al caricamento accade il contrario.


1

Sei confuso da "Tratta i file in iCloud allo stesso modo in cui tratti tutti gli altri file nella sandbox dell'app". Questo vale per qualcosa come Keynote e Numbers in cui mantieni un mucchio di file e, se hai iCloud, iniziano a sincronizzarsi magicamente.

Tuttavia, stai creando qualcosa che dipende dalla funzionalità simile a iCloud. Non puoi mantenere questa affermazione perché la tua app dipende da iCloud per essere presente affinché qualsiasi cosa funzioni nel modo previsto. Dovrai chiudere la tua app e dire semplicemente "configura iCloud affinché funzioni" o duplicare funzionalità simili a iCloud (la tua o quella di qualcun altro) che puoi sempre utilizzare, a prescindere.


Grazie. Quindi immagino di dover scegliere se fare un'app solo per iCloud o una sorta di ibrido per le persone che utilizzano le funzionalità di iCloud. Dato che iCloud è così complesso, tendo a scegliere un'app solo per iCloud. Grazie.
n.evermind
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.