Uso di iCloud con Xamarin.iOS

L'API di archiviazione iCloud in iOS 5 consente alle applicazioni di salvare i documenti utente e i dati specifici dell'applicazione in una posizione centrale e di accedere a tali elementi da tutti i dispositivi dell'utente.

Sono disponibili quattro tipi di spazio di archiviazione:

  • Archiviazione chiave-valore: per condividere piccole quantità di dati con l'applicazione in altri dispositivi di un utente.

  • Archiviazione UIDocument: per archiviare documenti e altri dati nell'account iCloud dell'utente usando una sottoclasse di UIDocument.

  • CoreData - Archiviazione di database SQLite.

  • Singoli file e directory : per la gestione di molti file diversi direttamente nel file system.

Questo documento illustra i primi due tipi, coppie chiave-valore e sottoclassi UIDocument, e come usare tali funzionalità in Xamarin.iOS.

Importante

Apple fornisce strumenti per aiutare gli sviluppatori a gestire correttamente il Regolamento generale sulla protezione dei dati (GDPR) dell'Unione Europea.

Requisiti

  • La versione stabile più recente di Xamarin.iOS
  • Xcode 10
  • Visual Studio per Mac o Visual Studio 2019.

Preparazione per lo sviluppo di iCloud

Le applicazioni devono essere configurate per l'uso di iCloud sia nel portale di provisioning Apple che nel progetto stesso. Prima di sviluppare per iCloud (o provare gli esempi) seguire questa procedura.

Per configurare correttamente un'applicazione per accedere a iCloud:

  • Trovare il teamID: accedere a developer.apple.com e visitare il Riepilogo dell'account per sviluppatore dell'account > al Centro > membri per ottenere l'ID team o l'ID singolo per i singoli sviluppatori. Si tratta di una stringa di 10 caratteri ( A93A5CM278 ad esempio), che fa parte dell'"identificatore del contenitore".

  • Creare un nuovo ID app: per creare un ID app, seguire i passaggi descritti nella sezione Provisioning per le tecnologie dello Store della guida Device Provisioning e assicurarsi di controllare iCloud come servizio consentito:

Controllare iCloud come servizio consentito

  • Creare un nuovo profilo di provisioning: per creare un profilo di provisioning, seguire la procedura descritta nella guida device provisioning .

  • Aggiungere l'identificatore del contenitore a Entitlements.plist . Il formato dell'identificatore del contenitore è TeamID.BundleID. Per altre informazioni, vedere la guida Uso dei diritti .

  • Configurare le proprietà del progetto: nel file Info.plist assicurarsi che l'identificatore del bundle corrisponda al set di ID bundle durante la creazione di un ID app; La firma del bundle iOS usa un profilo di provisioning che contiene un ID app con il servizio app iCloud e il file Entitlement personalizzati selezionato. Questa operazione può essere eseguita in Visual Studio nel riquadro Proprietà del progetto.

  • Abilitare iCloud nel dispositivo : passare a Impostazioni > iCloud e assicurarsi che il dispositivo sia connesso. Selezionare e attivare l'opzione Documenti e dati .

  • È necessario usare un dispositivo per testare iCloud . Non funzionerà nel simulatore. Infatti, hai davvero bisogno di due o più dispositivi tutti connessi con lo stesso ID Apple per vedere iCloud in azione.

Archiviazione chiave-valore

L'archiviazione chiave-valore è destinata a piccole quantità di dati che un utente potrebbe essere salvato in modo permanente tra i dispositivi, ad esempio l'ultima pagina visualizzata in un libro o in una rivista. L'archiviazione chiave-valore non deve essere usata per il backup dei dati.

Esistono alcune limitazioni da tenere presenti quando si usa l'archiviazione chiave-valore:

  • Dimensione massima della chiave: i nomi delle chiavi non possono superare i 64 byte.

  • Dimensione massima del valore: non è possibile archiviare più di 64 kilobyte in un singolo valore.

  • Dimensioni massime dell'archivio chiave-valore per un'app : le applicazioni possono archiviare fino a 64 kilobyte di dati chiave-valore in totale. I tentativi di impostare chiavi oltre tale limite avranno esito negativo e il valore precedente persisterà.

  • Tipi di dati: è possibile archiviare solo tipi di base come stringhe, numeri e valori booleani.

L'esempio iCloudKeyValue illustra il funzionamento. Il codice di esempio crea una chiave denominata per ogni dispositivo: è possibile impostare questa chiave in un dispositivo e osservare che il valore viene propagato ad altri. Crea anche una chiave denominata "Shared" che può essere modificata in qualsiasi dispositivo. Se modifichi in più dispositivi contemporaneamente, iCloud deciderà quale valore "vince" (usando un timestamp sulla modifica) e viene propagato.

Questo screenshot mostra l'esempio in uso. Quando le notifiche di modifica vengono ricevute da iCloud, vengono stampate nella visualizzazione testo di scorrimento nella parte inferiore dello schermo e aggiornate nei campi di input.

Flusso di messaggi tra dispositivi

Impostazione e recupero dei dati

Questo codice illustra come impostare un valore stringa.

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.SetString("testkey", "VALUE IN THE CLOUD");  // key and value
store.Synchronize();

La chiamata a Synchronize garantisce che il valore venga salvato in modo permanente solo nell'archiviazione su disco locale. La sincronizzazione con iCloud avviene in background e non può essere "forzata" dal codice dell'applicazione. Con una buona connettività di rete, la sincronizzazione si verifica spesso entro 5 secondi, tuttavia, se la rete è scarsa (o disconnessa) un aggiornamento potrebbe richiedere molto più tempo.

È possibile recuperare un valore con questo codice:

var store = NSUbiquitousKeyValueStore.DefaultStore;
display.Text = store.GetString("testkey");

Il valore viene recuperato dall'archivio dati locale: questo metodo non tenta di contattare i server iCloud per ottenere il valore "latest". iCloud aggiornerà l'archivio dati locale in base alla propria pianificazione.

Eliminazione di dati

Per rimuovere completamente una coppia chiave-valore, usare il metodo Remove come segue:

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.Remove("testkey");
store.Synchronize();

Osservazione delle modifiche

Un'applicazione può anche ricevere notifiche quando i valori vengono modificati da iCloud aggiungendo un osservatore a NSNotificationCenter.DefaultCenter. Il codice seguente del metodo KeyValueViewController.cs ViewWillAppear mostra come restare in ascolto di tali notifiche e creare un elenco delle chiavi modificate:

keyValueNotification =
NSNotificationCenter.DefaultCenter.AddObserver (
    NSUbiquitousKeyValueStore.DidChangeExternallyNotification, notification => {
    Console.WriteLine ("Cloud notification received");
    NSDictionary userInfo = notification.UserInfo;

    var reasonNumber = (NSNumber)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangeReasonKey);
    nint reason = reasonNumber.NIntValue;

    var changedKeys = (NSArray)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangedKeysKey);
    var changedKeysList = new List<string> ();
    for (uint i = 0; i < changedKeys.Count; i++) {
        var key = changedKeys.GetItem<NSString> (i); // resolve key to a string
        changedKeysList.Add (key);
    }
    // now do something with the list...
});

Il codice può quindi eseguire un'azione con l'elenco delle chiavi modificate, ad esempio l'aggiornamento di una copia locale di tali chiavi o l'aggiornamento dell'interfaccia utente con i nuovi valori.

I possibili motivi di modifica sono: ServerChange (0), InitialSyncChange (1) o QuotaViolationChange (2). È possibile accedere al motivo ed eseguire un'elaborazione diversa se necessario( ad esempio, potrebbe essere necessario rimuovere alcune chiavi in seguito a quotaViolationChange).

Archiviazione documenti

Archiviazione documenti iCloud è progettata per gestire i dati importanti per l'app (e per l'utente). Può essere usato per gestire file e altri dati necessari per l'esecuzione dell'app, fornendo allo stesso tempo funzionalità di backup e condivisione basate su iCloud in tutti i dispositivi dell'utente.

Questo diagramma mostra come si adatta tutto insieme. Ogni dispositivo ha dati salvati nell'archiviazione locale (UbiquityContainer) e il Daemon iCloud del sistema operativo si occupa dell'invio e della ricezione di dati nel cloud. Tutti gli accessi ai file a UbiquityContainer devono essere eseguiti tramite FilePresenter/FileCoordinator per impedire l'accesso simultaneo. La UIDocument classe implementa gli elementi desiderati. In questo esempio viene illustrato come usare UIDocument.

Panoramica dell'archiviazione dei documenti

L'esempio iCloudUIDoc implementa una sottoclasse semplice UIDocument che contiene un singolo campo di testo. Il rendering del testo viene eseguito in un UITextView oggetto e le modifiche vengono propagate da iCloud ad altri dispositivi con un messaggio di notifica visualizzato in rosso. Il codice di esempio non gestisce funzionalità iCloud più avanzate come la risoluzione dei conflitti.

Questo screenshot mostra l'applicazione di esempio: dopo aver modificato il testo e premendo UpdateChangeCount il documento viene sincronizzato tramite iCloud con altri dispositivi.

Questo screenshot mostra l'applicazione di esempio dopo la modifica del testo e la pressione di UpdateChangeCount

L'esempio iCloudUIDoc include cinque parti:

  1. Accesso a UbiquityContainer : determinare se iCloud è abilitato e, in tal caso, il percorso dell'area di archiviazione iCloud dell'applicazione.

  2. Creazione di una sottoclasse UIDocument: creare una classe a intermedio tra l'archiviazione iCloud e gli oggetti modello.

  3. Ricerca e apertura di documenti iCloud: usare NSFileManager e NSPredicate trovare documenti iCloud e aprirli.

  4. Visualizzazione di documenti iCloud: esporre le proprietà dall'utente UIDocument in modo da poter interagire con i controlli dell'interfaccia utente.

  5. Salvataggio di documenti iCloud: assicurarsi che le modifiche apportate nell'interfaccia utente siano persistenti su disco e iCloud.

Tutte le operazioni iCloud vengono eseguite (o devono essere eseguite) in modo asincrono in modo che non si blocchino durante l'attesa di un evento. Nell'esempio vengono visualizzati tre modi diversi per eseguire questa operazione:

Thread: nella AppDelegate.FinishedLaunching chiamata iniziale a GetUrlForUbiquityContainer viene eseguito su un altro thread per impedire il blocco del thread principale.

NotificationCenter : registrazione per le notifiche quando vengono completate operazioni asincrone, ad NSMetadataQuery.StartQuery esempio.

Gestori di completamento: passando i metodi da eseguire al completamento di operazioni asincrone come UIDocument.Open.

Accesso a UbiquityContainer

Il primo passaggio nell'uso dell'archiviazione documenti iCloud consiste nel determinare se iCloud è abilitato e, in tal caso, la posizione del "contenitore ubiquity" (la directory in cui sono archiviati i file abilitati per iCloud nel dispositivo).

Questo codice è nel AppDelegate.FinishedLaunching metodo dell'esempio.

// GetUrlForUbiquityContainer is blocking, Apple recommends background thread or your UI will freeze
ThreadPool.QueueUserWorkItem (_ => {
    CheckingForiCloud = true;
    Console.WriteLine ("Checking for iCloud");
    var uburl = NSFileManager.DefaultManager.GetUrlForUbiquityContainer (null);
    // OR instead of null you can specify "TEAMID.com.your-company.ApplicationName"

    if (uburl == null) {
        HasiCloud = false;
        Console.WriteLine ("Can't find iCloud container, check your provisioning profile and entitlements");

        InvokeOnMainThread (() => {
            var alertController = UIAlertController.Create ("No \uE049 available",
            "Check your Entitlements.plist, BundleId, TeamId and Provisioning Profile!", UIAlertControllerStyle.Alert);
            alertController.AddAction (UIAlertAction.Create ("OK", UIAlertActionStyle.Destructive, null));
            viewController.PresentViewController (alertController, false, null);
        });
    } else { // iCloud enabled, store the NSURL for later use
        HasiCloud = true;
        iCloudUrl = uburl;
        Console.WriteLine ("yyy Yes iCloud! {0}", uburl.AbsoluteUrl);
    }
    CheckingForiCloud = false;
});

Anche se l'esempio non lo fa, Apple consiglia di chiamare GetUrlForUbiquityContainer ogni volta che un'app viene in primo piano.

Creazione di una sottoclasse UIDocument

Tutti i file e le directory iCloud (ad esempio qualsiasi elemento archiviato nella directory UbiquityContainer) devono essere gestiti usando i metodi NSFileManager, implementando il protocollo NSFilePresenter e scrivendo tramite un NSFileCoordinator. Il modo più semplice per eseguire tutto ciò non è scriverlo manualmente, ma sottoclasse UIDocument che fa tutto per te.

Esistono solo due metodi che è necessario implementare in una sottoclasse UIDocument per usare iCloud:

  • LoadFromContents : passa il valore NSData del contenuto del file da decomprimere nella classe/es del modello.

  • ContentsForType : richiedere di fornire la rappresentazione NSData della classe/es del modello da salvare su disco (e cloud).

Questo codice di esempio di iCloudUIDoc\MonkeyDocument.cs illustra come implementare UIDocument.

public class MonkeyDocument : UIDocument
{
    // the 'model', just a chunk of text in this case; must easily convert to NSData
    NSString dataModel;
    // model is wrapped in a nice .NET-friendly property
    public string DocumentString {
        get {
            return dataModel.ToString ();
        }
        set {
            dataModel = new NSString (value);
        }
    }
    public MonkeyDocument (NSUrl url) : base (url)
    {
        DocumentString = "(default text)";
    }
    // contents supplied by iCloud to display, update local model and display (via notification)
    public override bool LoadFromContents (NSObject contents, string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("LoadFromContents({0})", typeName);

        if (contents != null)
            dataModel = NSString.FromData ((NSData)contents, NSStringEncoding.UTF8);

        // LoadFromContents called when an update occurs
        NSNotificationCenter.DefaultCenter.PostNotificationName ("monkeyDocumentModified", this);
        return true;
    }
    // return contents for iCloud to save (from the local model)
    public override NSObject ContentsForType (string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("ContentsForType({0})", typeName);
        Console.WriteLine ("DocumentText:{0}",dataModel);

        NSData docData = dataModel.Encode (NSStringEncoding.UTF8);
        return docData;
    }
}

Il modello di dati in questo caso è molto semplice, ovvero un singolo campo di testo. Il modello di dati può essere complesso come richiesto, ad esempio un documento XML o dati binari. Il ruolo principale dell'implementazione di UIDocument consiste nel tradurre tra le classi del modello e una rappresentazione NSData che può essere salvata/caricata su disco.

Ricerca e apertura di documenti iCloud

L'app di esempio gestisce solo un singolo file, test.txt, quindi il codice in AppDelegate.cs crea un oggetto NSPredicate e NSMetadataQuery per cercare in modo specifico tale nome file. L'oggetto NSMetadataQuery viene eseguito in modo asincrono e invia una notifica al termine. DidFinishGathering ottiene chiamato dall'osservatore di notifica, arresta la query e chiama LoadDocument, che usa il UIDocument.Open metodo con un gestore di completamento per tentare di caricare il file e visualizzarlo in un MonkeyDocumentViewControlleroggetto .

string monkeyDocFilename = "test.txt";
void FindDocument ()
{
    Console.WriteLine ("FindDocument");
    query = new NSMetadataQuery {
        SearchScopes = new NSObject [] { NSMetadataQuery.UbiquitousDocumentsScope }
    };

    var pred = NSPredicate.FromFormat ("%K == %@", new NSObject[] {
        NSMetadataQuery.ItemFSNameKey, new NSString (MonkeyDocFilename)
    });

    Console.WriteLine ("Predicate:{0}", pred.PredicateFormat);
    query.Predicate = pred;

    NSNotificationCenter.DefaultCenter.AddObserver (
        this,
        new Selector ("queryDidFinishGathering:"),
        NSMetadataQuery.DidFinishGatheringNotification,
        query
    );

    query.StartQuery ();
}

[Export ("queryDidFinishGathering:")]
void DidFinishGathering (NSNotification notification)
{
    Console.WriteLine ("DidFinishGathering");
    var metadataQuery = (NSMetadataQuery)notification.Object;
    metadataQuery.DisableUpdates ();
    metadataQuery.StopQuery ();

    NSNotificationCenter.DefaultCenter.RemoveObserver (this, NSMetadataQuery.DidFinishGatheringNotification, metadataQuery);
    LoadDocument (metadataQuery);
}

void LoadDocument (NSMetadataQuery metadataQuery)
{
    Console.WriteLine ("LoadDocument");

    if (metadataQuery.ResultCount == 1) {
        var item = (NSMetadataItem)metadataQuery.ResultAtIndex (0);
        var url = (NSUrl)item.ValueForAttribute (NSMetadataQuery.ItemURLKey);
        doc = new MonkeyDocument (url);

        doc.Open (success => {
            if (success) {
                Console.WriteLine ("iCloud document opened");
                Console.WriteLine (" -- {0}", doc.DocumentString);
                viewController.DisplayDocument (doc);
            } else {
                Console.WriteLine ("failed to open iCloud document");
            }
        });
    } // TODO: if no document, we need to create one
}

Visualizzazione di documenti iCloud

La visualizzazione di un uiDocument non deve essere diversa da qualsiasi altra classe del modello. Le proprietà vengono visualizzate nei controlli dell'interfaccia utente, eventualmente modificate dall'utente e quindi riscritto nel modello.

Nell'esempio iCloudUIDoc\MonkeyDocumentViewController.cs visualizza il testo MonkeyDocument in un oggetto UITextView. ViewDidLoad è in ascolto della notifica inviata nel MonkeyDocument.LoadFromContents metodo . LoadFromContents viene chiamato quando iCloud ha nuovi dati per il file, in modo che la notifica indichi che il documento è stato aggiornato.

NSNotificationCenter.DefaultCenter.AddObserver (this,
    new Selector ("dataReloaded:"),
    new NSString ("monkeyDocumentModified"),
    null
);

Il gestore di notifica del codice di esempio chiama un metodo per aggiornare l'interfaccia utente, in questo caso senza alcun rilevamento o risoluzione dei conflitti.

[Export ("dataReloaded:")]
void DataReloaded (NSNotification notification)
{
    doc = (MonkeyDocument)notification.Object;
    // we just overwrite whatever was being typed, no conflict resolution for now
    docText.Text = doc.DocumentString;
}

Salvataggio di documenti iCloud

Per aggiungere un UIDocument a iCloud, è possibile chiamare UIDocument.Save direttamente (solo per i nuovi documenti) o spostare un file esistente usando NSFileManager.DefaultManager.SetUbiquitious. Il codice di esempio crea un nuovo documento direttamente nel contenitore ubiquity con questo codice (qui sono presenti due gestori di completamento, uno per l'operazione Save e un altro per Open):

var docsFolder = Path.Combine (iCloudUrl.Path, "Documents"); // NOTE: Documents folder is user-accessible in Settings
var docPath = Path.Combine (docsFolder, MonkeyDocFilename);
var ubiq = new NSUrl (docPath, false);
var monkeyDoc = new MonkeyDocument (ubiq);
monkeyDoc.Save (monkeyDoc.FileUrl, UIDocumentSaveOperation.ForCreating, saveSuccess => {
Console.WriteLine ("Save completion:" + saveSuccess);
if (saveSuccess) {
    monkeyDoc.Open (openSuccess => {
        Console.WriteLine ("Open completion:" + openSuccess);
        if (openSuccess) {
            Console.WriteLine ("new document for iCloud");
            Console.WriteLine (" == " + monkeyDoc.DocumentString);
            viewController.DisplayDocument (monkeyDoc);
        } else {
            Console.WriteLine ("couldn't open");
        }
    });
} else {
    Console.WriteLine ("couldn't save");
}

Le modifiche successive al documento non vengono "salvate" direttamente, ma si indica UIDocument che è stata modificata con UpdateChangeCounte pianifica automaticamente un'operazione di salvataggio su disco:

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

Gestione dei documenti iCloud

Gli utenti possono gestire i documenti iCloud nella directory Documenti del "contenitore ubiquity" all'esterno dell'applicazione tramite Impostazioni; possono visualizzare l'elenco di file e scorrere rapidamente per eliminare. Il codice dell'applicazione deve essere in grado di gestire la situazione in cui i documenti vengono eliminati dall'utente. Non archiviare i dati interni dell'applicazione nella directory Documenti .

Gestione del flusso di lavoro documenti iCloud

Gli utenti riceveranno anche avvisi diversi quando tentano di rimuovere un'applicazione abilitata per iCloud dal dispositivo, per informarli dello stato dei documenti iCloud correlati a tale applicazione.

Screenshot che mostra un avviso per Gli aggiornamenti dei documenti in sospeso.

Screenshot che mostra un avviso per Delete i Cloud.

Backup di iCloud

Durante il backup in iCloud non è una funzionalità a cui si accede direttamente dagli sviluppatori, il modo in cui si progetta l'applicazione può influire sull'esperienza utente. Apple fornisce linee guida per l'archiviazione dei dati iOS che gli sviluppatori possono seguire nelle applicazioni iOS.

La considerazione più importante è se l'app archivia file di grandi dimensioni che non vengono generati dall'utente (ad esempio, un'applicazione lettore di riviste che archivia centinaia di megabyte di contenuto per ogni problema). Apple preferisce che non archivii questo tipo di dati in cui verrà eseguito il backup in iCloud e riempia inutilmente la quota iCloud dell'utente.

Le applicazioni che archivia grandi quantità di dati come questa devono archiviarla in una delle directory utente di cui non è stato eseguito il backup ( ad esempio. Memorizza nella cache o tmp) o usa NSFileManager.SetSkipBackupAttribute per applicare un flag a tali file in modo che iCloud li ignori durante le operazioni di backup.

Riepilogo

Questo articolo ha introdotto la nuova funzionalità iCloud inclusa in iOS 5. Ha esaminato i passaggi necessari per configurare il progetto per l'uso di iCloud e quindi ha fornito esempi di come implementare le funzionalità di iCloud.

L'esempio di archiviazione chiave-valore ha illustrato come usare iCloud per archiviare una piccola quantità di dati simile alla modalità di archiviazione di NSUserPreferences. L'esempio uiDocument ha illustrato come archiviare e sincronizzare i dati più complessi tra più dispositivi tramite iCloud.

Infine, è stata inclusa una breve discussione sul modo in cui l'aggiunta di iCloud Backup dovrebbe influenzare la progettazione dell'applicazione.