Utilisation d’iCloud avec Xamarin.iOS

L’API de stockage iCloud dans iOS 5 permet aux applications d’enregistrer des documents utilisateur et des données spécifiques à l’application dans un emplacement central et d’accéder à ces éléments à partir de tous les appareils de l’utilisateur.

Il existe quatre types de stockage disponibles :

  • Stockage clé-valeur pour partager de petites quantités de données avec votre application sur les autres appareils d’un utilisateur.

  • Stockage UIDocument : pour stocker des documents et d’autres données dans le compte iCloud de l’utilisateur à l’aide d’une sous-classe d’UIDocument.

  • CoreData - Stockage de base de données SQLite.

  • Fichiers et répertoires individuels : pour gérer un grand nombre de fichiers différents directement dans le système de fichiers.

Ce document décrit les deux premiers types - paires clé-valeur et sous-classes UIDocument - et explique comment utiliser ces fonctionnalités dans Xamarin.iOS.

Important

Apple fournit des outils pour aider les développeurs à gérer correctement le Règlement général sur la protection des données (RGPD) de l’Union européenne.

Spécifications

  • La dernière version stable de Xamarin.iOS
  • Xcode 10
  • Visual Studio pour Mac ou Visual Studio 2019.

Préparation au développement iCloud

Les applications doivent être configurées pour utiliser iCloud à la fois dans le portail Apple Provisioning et le projet lui-même. Avant de développer pour iCloud (ou d’essayer les exemples), suivez les étapes ci-dessous.

Pour configurer correctement une application pour accéder à iCloud :

  • Recherchez votre TeamID : connectez-vous à developer.apple.com et visitez le Résumé du compte > développeur du centre de membres > pour obtenir votre ID d’équipe (ou ID individuel pour les développeurs uniques). Il s’agit d’une chaîne de 10 caractères ( A93A5CM278 par exemple), qui fait partie de l'« identificateur de conteneur ».

  • Créez un ID d’application : pour créer un ID d’application, suivez les étapes décrites dans la section Approvisionnement pour les technologies du Store du guide device Provisioning et veillez à case activée iCloud en tant que service autorisé :

Check iCloud as an allowed service

  • Créez un profil d’approvisionnement : pour créer un profil d’approvisionnement, suivez les étapes décrites dans le guide device Provisioning .

  • Ajoutez l’identificateur de conteneur à Entitlements.plist : le format d’identificateur de conteneur est TeamID.BundleID. Pour plus d’informations, consultez le guide Utilisation des droits d’utilisation.

  • Configurez les propriétés du projet : dans le fichier Info.plist, vérifiez que l’identificateur de bundle correspond à l’ID de bundle défini lors de la création d’un ID d’application ; La signature groupée iOS utilise un profil d’approvisionnement qui contient un ID d’application avec iCloud App Service et le fichier droits personnalisés sélectionné. Cette opération peut être effectuée dans Visual Studio sous le volet Propriétés du projet.

  • Activez iCloud sur votre appareil : accédez à Paramètres > iCloud et vérifiez que l’appareil est connecté. Sélectionnez et activez l’option Documents &Données .

  • Vous devez utiliser un appareil pour tester iCloud . Il ne fonctionnera pas sur le simulateur. En fait, vous avez vraiment besoin de deux appareils ou plus tous connectés avec le même ID Apple pour voir iCloud en action.

Stockage clé-valeur

Le stockage clé-valeur est destiné à de petites quantités de données qu’un utilisateur peut aimer conserver sur les appareils, comme la dernière page qu’il a vue dans un livre ou un magazine. Le stockage clé-valeur ne doit pas être utilisé pour la sauvegarde des données.

Il existe certaines limitations à prendre en compte lors de l’utilisation du stockage clé-valeur :

  • Taille maximale de la clé : les noms de clés ne peuvent pas dépasser 64 octets.

  • Taille maximale de la valeur : vous ne pouvez pas stocker plus de 64 kilo-octets dans une seule valeur.

  • Taille maximale du magasin de valeurs clés pour une application : les applications ne peuvent stocker que jusqu’à 64 kilo-octets de données clé-valeur au total. Les tentatives de définition des clés au-delà de cette limite échouent et la valeur précédente persiste.

  • Types de données : seuls les types de base tels que les chaînes, les nombres et les booléens peuvent être stockés.

L’exemple iCloudKeyValue montre comment il fonctionne. L’exemple de code crée une clé nommée pour chaque appareil : vous pouvez définir cette clé sur un appareil et regarder la valeur se propager à d’autres personnes. Il crée également une clé appelée « Partagé » qui peut être modifiée sur n’importe quel appareil , si vous modifiez sur de nombreux appareils à la fois, iCloud décidera de la valeur « wins » (à l’aide d’un horodatage sur la modification) et se propage.

Cette capture d’écran montre l’exemple utilisé. Lorsque les notifications de modification sont reçues d’iCloud, elles sont imprimées dans l’affichage de texte de défilement en bas de l’écran et mises à jour dans les champs d’entrée.

The flow of messages between devices

Définition et récupération des données

Ce code montre comment définir une valeur de chaîne.

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

L’appel synchrone garantit que la valeur est conservée uniquement dans le stockage de disque local. La synchronisation avec iCloud se produit en arrière-plan et ne peut pas être « forcée » par le code de l’application. Avec une bonne connectivité réseau, la synchronisation se produit souvent dans les 5 secondes, mais si le réseau est médiocre (ou déconnecté) une mise à jour peut prendre beaucoup plus de temps.

Vous pouvez récupérer une valeur avec ce code :

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

La valeur est récupérée à partir du magasin de données local : cette méthode ne tente pas de contacter les serveurs iCloud pour obtenir la valeur « la plus récente ». iCloud met à jour le magasin de données local selon sa propre planification.

Suppression de données

Pour supprimer complètement une paire clé-valeur, utilisez la méthode Remove comme suit :

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

Observation des modifications

Une application peut également recevoir des notifications lorsque les valeurs sont modifiées par iCloud en ajoutant un observateur au NSNotificationCenter.DefaultCenter. Le code suivant de la méthode KeyValueViewController.csViewWillAppear montre comment écouter ces notifications et créer une liste des clés qui ont été modifiées :

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...
});

Votre code peut ensuite effectuer une action avec la liste des clés modifiées, telles que la mise à jour d’une copie locale de ces clés ou la mise à jour de l’interface utilisateur avec les nouvelles valeurs.

Les raisons de modification possibles sont les suivantes : ServerChange (0), InitialSyncChange (1) ou QuotaViolationChange (2). Vous pouvez accéder à la raison et effectuer un traitement différent si nécessaire (par exemple, vous devrez peut-être supprimer certaines clés à la suite d’un QuotaViolationChange).

Document Stockage

iCloud Document Stockage est conçu pour gérer les données importantes pour votre application (et pour l’utilisateur). Il peut être utilisé pour gérer des fichiers et d’autres données que votre application doit exécuter, tout en fournissant en même temps des fonctionnalités de sauvegarde et de partage iCloud sur tous les appareils de l’utilisateur.

Ce diagramme montre comment il s’adapte tous. Chaque appareil dispose de données enregistrées sur le stockage local (UbiquityContainer) et le démon iCloud du système d’exploitation s’occupe de l’envoi et de la réception de données dans le cloud. Tout l’accès au fichier UbiquityContainer doit être effectué via FilePresenter/FileCoordinator pour empêcher l’accès simultané. La UIDocument classe implémente ces éléments pour vous ; cet exemple montre comment utiliser UIDocument.

The document storage overview

L’exemple iCloudUIDoc implémente une sous-classe simple UIDocument qui contient un seul champ de texte. Le texte est affiché dans un UITextView iCloud et les modifications sont propagés par iCloud à d’autres appareils avec un message de notification affiché en rouge. L’exemple de code ne traite pas des fonctionnalités iCloud plus avancées telles que la résolution des conflits.

Cette capture d’écran montre l’exemple d’application : après avoir modifié le texte et appuyé sur UpdateChangeCount , le document est synchronisé via iCloud sur d’autres appareils.

This screenshot shows the sample application after changing the text and pressing UpdateChangeCount

Il existe cinq parties à l’exemple iCloudUIDoc :

  1. Accès à UbiquityContainer : déterminez si iCloud est activé et, si tel est le cas, le chemin d’accès à la zone de stockage iCloud de votre application.

  2. Création d’une sous-classe UIDocument : créez une classe à intermédiaire entre le stockage iCloud et vos objets de modèle.

  3. Recherche et ouverture de documents iCloud : utilisez NSFileManager et NSPredicate recherchez des documents iCloud et ouvrez-les.

  4. Affichage de documents iCloud : exposez les propriétés à partir de votre UIDocument appareil afin que vous puissiez interagir avec les contrôles d’interface utilisateur.

  5. Enregistrement de documents iCloud : vérifiez que les modifications apportées dans l’interface utilisateur sont conservées sur le disque et sur iCloud.

Toutes les opérations iCloud s’exécutent (ou doivent s’exécuter) de façon asynchrone afin qu’elles ne bloquent pas en attendant que quelque chose se produise. Vous verrez trois façons différentes d’effectuer cette opération dans l’exemple :

Threads : dans AppDelegate.FinishedLaunching l’appel initial à lequel GetUrlForUbiquityContainer est effectué sur un autre thread pour empêcher le blocage du thread principal.

NotificationCenter : inscription aux notifications lorsque des opérations asynchrones telles que NSMetadataQuery.StartQuery terminées.

Gestionnaires d’achèvement : passage de méthodes pour s’exécuter à l’achèvement d’opérations asynchrones telles que UIDocument.Open.

Accès à UbiquityContainer

La première étape de l’utilisation d’iCloud Document Stockage consiste à déterminer si iCloud est activé et, si tel est le cas, l’emplacement du « conteneur ubiquity » (répertoire dans lequel les fichiers iCloud sont stockés sur l’appareil).

Ce code se trouve dans la AppDelegate.FinishedLaunching méthode de l’exemple.

// 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;
});

Bien que l’exemple ne le fasse pas, Apple recommande d’appeler GetUrlForUbiquityContainer chaque fois qu’une application vient au premier plan.

Création d’une sous-classe UIDocument

Tous les fichiers et répertoires iCloud (par exemple, tout ce qui est stocké dans le répertoire UbiquityContainer) doivent être gérés à l’aide de méthodes NSFileManager, implémentant le protocole NSFilePresenter et en écrivant via un NSFileCoordinator. La façon la plus simple de faire tout cela n’est pas de l’écrire vous-même, mais sous-classe UIDocument qui fait tout pour vous.

Il n’existe que deux méthodes que vous devez implémenter dans une sous-classe UIDocument pour utiliser iCloud :

  • LoadFromContents : passe le NSData du contenu du fichier pour vous permettre de décompresser dans votre classe de modèle/es.

  • ContentsForType : demandez-vous de fournir la représentation NSData de votre classe de modèle/es à enregistrer sur le disque (et le cloud).

Cet exemple de code à partir d’iCloudUIDoc \MonkeyDocument.cs montre comment implémenter 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;
    }
}

Le modèle de données dans ce cas est très simple : un seul champ de texte. Votre modèle de données peut être aussi complexe que nécessaire, tel qu’un document Xml ou des données binaires. Le rôle principal de l’implémentation UIDocument consiste à traduire entre vos classes de modèle et une représentation NSData qui peut être enregistrée/chargée sur le disque.

Recherche et ouverture de documents iCloud

L’exemple d’application traite uniquement d’un seul fichier - test.txt - de sorte que le code dans AppDelegate.cs crée et NSPredicateNSMetadataQuery recherche spécifiquement ce nom de fichier. L’exécution NSMetadataQuery s’exécute de manière asynchrone et envoie une notification lorsqu’elle se termine. DidFinishGathering est appelé par l’observateur de notification, arrête la requête et appelle LoadDocument, qui utilise la UIDocument.Open méthode avec un gestionnaire d’achèvement pour tenter de charger le fichier et de l’afficher dans un MonkeyDocumentViewController.

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
}

Affichage de documents iCloud

L’affichage d’un UIDocument ne doit pas être différent de toute autre classe de modèle : les propriétés sont affichées dans les contrôles d’interface utilisateur, éventuellement modifiées par l’utilisateur, puis réécrites dans le modèle.

Dans l’exemple iCloudUIDoc\MonkeyDocumentViewController.cs affiche le texte MonkeyDocument dans un UITextView. ViewDidLoad écoute la notification envoyée dans la MonkeyDocument.LoadFromContents méthode. LoadFromContents est appelé lorsque iCloud a de nouvelles données pour le fichier, de sorte que la notification indique que le document a été mis à jour.

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

L’exemple de gestionnaire de notification de code appelle une méthode pour mettre à jour l’interface utilisateur , dans ce cas sans détection ou résolution de conflit.

[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;
}

Enregistrement de documents iCloud

Pour ajouter un UIDocument à iCloud, vous pouvez appeler UIDocument.Save directement (pour les nouveaux documents uniquement) ou déplacer un fichier existant à l’aide NSFileManager.DefaultManager.SetUbiquitiousde . L’exemple de code crée un document directement dans le conteneur ubiquity avec ce code (il existe deux gestionnaires d’achèvement ici, un pour l’opération Save et un autre pour l’ouverture) :

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");
}

Les modifications ultérieures apportées au document ne sont pas « enregistrées » directement, au lieu de cela, nous disons UIDocument qu’elle a changé avec UpdateChangeCount, et elle planifie automatiquement une opération d’enregistrement sur le disque :

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

Gestion des documents iCloud

Les utilisateurs peuvent gérer des documents iCloud dans le répertoire Documents du « conteneur ubiquity » en dehors de votre application via Paramètres ; ils peuvent afficher la liste des fichiers et balayer pour supprimer. Le code d’application doit être en mesure de gérer la situation dans laquelle les documents sont supprimés par l’utilisateur. Ne stockez pas les données d’application interne dans le répertoire Documents .

Managing iCloud Documents workflow

Les utilisateurs recevront également différents avertissements lorsqu’ils tentent de supprimer une application compatible iCloud de leur appareil, afin de les informer de l’état des documents iCloud liés à cette application.

Screenshot shows a warning for Document Updates Pending.

Screenshot shows a warning for Delete i Cloud.

Sauvegarde iCloud

Bien que la sauvegarde sur iCloud ne soit pas une fonctionnalité directement accessible par les développeurs, la façon dont vous concevez votre application peut affecter l’expérience utilisateur. Apple fournit des instructions de Stockage de données iOS pour les développeurs à suivre dans leurs applications iOS.

La considération la plus importante est de savoir si votre application stocke les fichiers volumineux qui ne sont pas générés par l’utilisateur (par exemple, une application de lecture de magazine qui stocke des centaines de mégaoctets de contenu par problème). Apple préfère que vous ne stockiez pas ce type de données là où elles seront sauvegardées sur iCloud et remplissent inutilement le quota iCloud de l’utilisateur.

Les applications qui stockent de grandes quantités de données telles que celles-ci doivent soit les stocker dans l’un des répertoires utilisateur qui ne sont pas sauvegardés (par exemple. Met en cache ou tmp) ou utilisez-le NSFileManager.SetSkipBackupAttribute pour appliquer un indicateur à ces fichiers afin que iCloud les ignore pendant les opérations de sauvegarde.

Résumé

Cet article a présenté la nouvelle fonctionnalité iCloud incluse dans iOS 5. Il a examiné les étapes requises pour configurer votre projet pour utiliser iCloud, puis fourni des exemples d’implémentation des fonctionnalités iCloud.

L’exemple de stockage clé-valeur a montré comment iCloud peut être utilisé pour stocker une petite quantité de données similaire à la façon dont les NSUserPreferences sont stockées. L’exemple UIDocument a montré comment des données plus complexes peuvent être stockées et synchronisées sur plusieurs appareils via iCloud.

Enfin, il a inclus une brève discussion sur la façon dont l’ajout de la sauvegarde iCloud doit influencer la conception de votre application.