Usando o iCloud com o Xamarin.iOS

A API de armazenamento do iCloud no iOS 5 permite que os aplicativos salvem documentos do usuário e dados específicos do aplicativo em um local central e acessem esses itens de todos os dispositivos do usuário.

Existem quatro tipos de armazenamento disponíveis:

  • Armazenamento de chave-valor - para compartilhar pequenas quantidades de dados com seu aplicativo em outros dispositivos de um usuário.

  • Armazenamento UIDocument - para armazenar documentos e outros dados na conta do iCloud do usuário usando uma subclasse de UIDocument.

  • CoreData - armazenamento de banco de dados SQLite.

  • Arquivos e diretórios individuais - para gerenciar muitos arquivos diferentes diretamente no sistema de arquivos.

Este documento discute os dois primeiros tipos – pares chave-valor e subclasses UIDocument – e como usar esses recursos no Xamarin.iOS.

Importante

A Apple fornece ferramentas para ajudar os desenvolvedores a lidar adequadamente com o GDPR (Regulamento Geral sobre a Proteção de Dados) da União Europeia.

Requisitos

  • A versão estável mais recente do Xamarin.iOS
  • Xcode 10
  • Visual Studio para Mac ou Visual Studio 2019.

Preparando-se para o desenvolvimento do iCloud

Os aplicativos devem ser configurados para usar o iCloud no Portal de Provisionamento da Apple e no próprio projeto. Antes de desenvolver para iCloud (ou experimentar as amostras), siga as etapas abaixo.

Para configurar corretamente um aplicativo para acessar o iCloud:

  • Encontre seu ID de equipe - faça login no developer.apple.com e visite o Resumo da conta de desenvolvedor da sua conta > da Central > de Membros para obter seu ID de equipe (ou ID individual para desenvolvedores individuais). Será uma string de 10 caracteres ( A93A5CM278 por exemplo) - isso faz parte do "identificador de contêiner".

  • Criar uma nova ID do Aplicativo – para criar uma ID do Aplicativo, siga as etapas descritas na seção Provisionamento para Tecnologias da Loja do guia de Provisionamento de Dispositivos e certifique-se de marcar o iCloud como um serviço permitido:

Verifique o iCloud como um serviço permitido

  • Criar um novo perfil de provisionamento - Para criar um perfil de provisionamento, siga as etapas descritas no guia de provisionamento de dispositivos.

  • Adicione o identificador de contêiner a Entitlements.plist - o formato do identificador de contêiner é TeamID.BundleID. Para obter mais informações, consulte o guia Trabalhando com direitos .

  • Configurar as propriedades do projeto – no arquivo Info.plist, verifique se o Identificador de Pacote corresponde à ID do Pacote definida ao criar uma ID do Aplicativo; A Assinatura de Pacote do iOS usa um Perfil de Provisionamento que contém uma ID do Aplicativo com o Serviço de Aplicativo do iCloud e o arquivo de Direitos Personalizados selecionado. Tudo isso pode ser feito no Visual Studio no painel Propriedades do projeto.

  • Ative o iCloud no seu dispositivo - vá para Configurações > iCloud e verifique se o dispositivo está conectado. Selecione e ative a opção Documentos e Dados .

  • Você deve usar um dispositivo para testar o iCloud - ele não funcionará no Simulador. Na verdade, você realmente precisa de dois ou mais dispositivos conectados com o mesmo ID Apple para ver o iCloud em ação.

Armazenamento de chave-valor

O armazenamento de chave-valor destina-se a pequenas quantidades de dados que um usuário pode gostar que persistam em todos os dispositivos, como a última página visualizada em um livro ou revista. O armazenamento de chave-valor não deve ser usado para fazer backup de dados.

Há algumas limitações a serem observadas ao usar o armazenamento de chave-valor:

  • Tamanho máximo da chave - Os nomes das chaves não podem ter mais de 64 bytes.

  • Tamanho máximo do valor - Você não pode armazenar mais de 64 kilobytes em um único valor.

  • Tamanho máximo do repositório de chave-valor para um aplicativo – os aplicativos só podem armazenar até 64 kilobytes de dados de chave-valor no total. As tentativas de definir chaves além desse limite falharão e o valor anterior persistirá.

  • Tipos de dados - Somente tipos básicos como strings, números e booleanos podem ser armazenados.

O exemplo iCloudKeyValue demonstra como ele funciona. O código de exemplo cria uma chave nomeada para cada dispositivo: você pode definir essa chave em um dispositivo e observar o valor ser propagado para outros. Ele também cria uma chave chamada "Compartilhada" que pode ser editada em qualquer dispositivo - se você editar em vários dispositivos ao mesmo tempo, o iCloud decidirá qual valor "vence" (usando um carimbo de data/hora na alteração) e será propagado.

Esta captura de tela mostra o exemplo em uso. Quando as notificações de alteração são recebidas do iCloud, elas são impressas na visualização de texto de rolagem na parte inferior da tela e atualizadas nos campos de entrada.

O fluxo de mensagens entre dispositivos

Configurando e recuperando dados

Este código mostra como definir um valor de cadeia de caracteres.

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

Chamar Synchronize garante que o valor seja mantido somente no armazenamento em disco local. A sincronização com o iCloud acontece em segundo plano e não pode ser "forçada" pelo código do aplicativo. Com uma boa conectividade de rede, a sincronização geralmente ocorre em 5 segundos, no entanto, se a rede estiver ruim (ou desconectada), uma atualização pode demorar muito mais.

Você pode recuperar um valor com este código:

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

O valor é recuperado do armazenamento de dados local - esse método não tenta entrar em contato com os servidores do iCloud para obter o valor "mais recente". O iCloud atualizará o armazenamento de dados local de acordo com sua própria programação.

Excluindo dados

Para remover completamente um par de chave-valor, use o método Remove assim:

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

Observando as mudanças

Um aplicativo também pode receber notificações quando os valores são alterados pelo iCloud adicionando um observador ao NSNotificationCenter.DefaultCenter. O código a seguir de KeyValueViewController.cs ViewWillAppear método mostra como escutar essas notificações e criar uma lista de quais chaves foram alteradas:

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

Seu código pode então executar alguma ação com a lista de chaves alteradas, como atualizar uma cópia local delas ou atualizar a interface do usuário com os novos valores.

Os possíveis motivos de alteração são: ServerChange (0), InitialSyncChange (1) ou QuotaViolationChange (2). Você pode acessar o motivo e executar um processamento diferente, se necessário (por exemplo, talvez seja necessário remover algumas chaves como resultado de um QuotaViolationChange).

Armazenamento de documentos

O Armazenamento de Documentos do iCloud foi projetado para gerenciar dados importantes para o app (e para o usuário). Ele pode ser usado para gerenciar arquivos e outros dados que seu aplicativo precisa para ser executado e, ao mesmo tempo, fornecer funcionalidade de backup e compartilhamento baseada em iCloud em todos os dispositivos do usuário.

Este diagrama mostra como tudo se encaixa. Cada dispositivo tem dados salvos no armazenamento local (o UbiquityContainer) e o iCloud Daemon do sistema operacional cuida do envio e recebimento de dados na nuvem. Todo o acesso de arquivo ao UbiquityContainer deve ser feito por meio de FilePresenter/FileCoordinator para impedir o acesso simultâneo. A UIDocument classe os implementa para você; este exemplo mostra como usar UIDocument.

A visão geral do armazenamento de documentos

O exemplo iCloudUIDoc implementa uma subclasse simples UIDocument que contém um único campo de texto. O texto é renderizado em um UITextView e as edições são propagadas pelo iCloud para outros dispositivos com uma mensagem de notificação mostrada em vermelho. O código de exemplo não lida com recursos mais avançados do iCloud, como resolução de conflitos.

Esta captura de tela mostra o aplicativo de exemplo - depois de alterar o texto e pressionar UpdateChangeCount , o documento é sincronizado via iCloud com outros dispositivos.

Esta captura de tela mostra o aplicativo de exemplo depois de alterar o texto e pressionar UpdateChangeCount

Há cinco partes no exemplo de iCloudUIDoc:

  1. Acessando o UbiquityContainer - determine se o iCloud está habilitado e, em caso afirmativo, o caminho para a área de armazenamento do iCloud do seu aplicativo.

  2. Criando uma subclasse UIDocument - crie uma classe para intermediar entre o armazenamento do iCloud e seus objetos de modelo.

  3. Encontrar e abrir documentos do iCloud - use NSFileManager e NSPredicate para encontrar documentos do iCloud e abri-los.

  4. Exibindo documentos do iCloud - exponha as propriedades do seu UIDocument para que você possa interagir com os controles da interface do usuário.

  5. Salvando documentos do iCloud - certifique-se de que as alterações feitas na interface do usuário sejam mantidas no disco e no iCloud.

Todas as operações do iCloud são executadas (ou devem ser executadas) de forma assíncrona para que não sejam bloqueadas enquanto aguardam que algo aconteça. Você verá três maneiras diferentes de fazer isso no exemplo:

Threads - na AppDelegate.FinishedLaunching chamada inicial para GetUrlForUbiquityContainer é feito em outro thread para evitar o bloqueio do thread principal.

NotificationCenter - registrando-se para notificações quando operações assíncronas, como NSMetadataQuery.StartQuery concluídas.

Manipuladores de conclusão - passando métodos a serem executados após a conclusão de operações assíncronas como UIDocument.Open.

Acessando o UbiquityContainer

O primeiro passo para usar o Armazenamento de Documentos do iCloud é determinar se o iCloud está ativado e, em caso afirmativo, a localização do "contêiner de onipresença" (o diretório onde os arquivos habilitados para iCloud são armazenados no dispositivo).

Esse código está no AppDelegate.FinishedLaunching método do exemplo.

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

Embora o exemplo não faça isso, a Apple recomenda chamar GetUrlForUbiquityContainer sempre que um aplicativo vier para o primeiro plano.

Criando uma subclasse UIDocument

Todos os arquivos e diretórios do iCloud (ou seja, qualquer coisa armazenada no diretório UbiquityContainer) devem ser gerenciados usando métodos NSFileManager, implementando o protocolo NSFilePresenter e gravando por meio de um NSFileCoordinator. A maneira mais simples de fazer tudo isso não é escrever você mesmo, mas a subclasse UIDocument, que faz tudo por você.

Existem apenas dois métodos que você deve implementar em uma subclasse UIDocument para trabalhar com o iCloud:

  • LoadFromContents - passa o NSData do conteúdo do arquivo para você descompactar em suas classes de modelo.

  • ContentsForType – solicite que você forneça a representação NSData de suas classes de modelo para salvar em disco (e na nuvem).

Este código de exemplo de iCloudUIDoc\MonkeyDocument.cs mostra como implementar 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;
    }
}

O modelo de dados, neste caso, é muito simples - um único campo de texto. Seu modelo de dados pode ser tão complexo quanto necessário, como um documento Xml ou dados binários. A principal função da implementação do UIDocument é traduzir entre suas classes de modelo e uma representação NSData que pode ser salva/carregada no disco.

Encontrando e abrindo documentos do iCloud

O aplicativo de exemplo lida apenas com um único arquivo - test.txt - portanto, o código em AppDelegate.cs cria um NSPredicate e NSMetadataQuery para procurar especificamente esse nome de arquivo. O é NSMetadataQuery executado de forma assíncrona e envia uma notificação quando termina. DidFinishGathering é chamado pelo observador de notificação, interrompe a consulta e chama LoadDocument, que usa o UIDocument.Open método com um manipulador de conclusão para tentar carregar o arquivo e exibi-lo em um 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
}

Exibindo documentos do iCloud

A exibição de um UIDocument não deve ser diferente de qualquer outra classe de modelo – as propriedades são exibidas em controles de interface do usuário, possivelmente editadas pelo usuário e, em seguida, gravadas de volta no modelo.

No exemplo , iCloudUIDoc\MonkeyDocumentViewController.cs exibe o texto MonkeyDocument em um UITextViewarquivo . ViewDidLoad escuta a notificação enviada no MonkeyDocument.LoadFromContents método. LoadFromContents é chamado quando o iCloud tem novos dados para o arquivo, de modo que a notificação indica que o documento foi atualizado.

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

O manipulador de notificação de código de exemplo chama um método para atualizar a interface do usuário - nesse caso, sem qualquer detecção ou resolução de conflito.

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

Salvando documentos do iCloud

Para adicionar um UIDocument ao iCloud, você pode ligar UIDocument.Save diretamente (somente para novos documentos) ou mover um arquivo existente usando NSFileManager.DefaultManager.SetUbiquitious. O código de exemplo cria um novo documento diretamente no contêiner ubiquity com este código (há dois manipuladores de conclusão aqui, um para a Save operação e outro para o 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");
}

As alterações subsequentes no documento não são "salvas" diretamente, em vez disso, informamos UIDocument que ele foi alterado com UpdateChangeCounto , e ele agendará automaticamente uma operação de salvamento em disco:

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

Gerenciando documentos do iCloud

Os usuários podem gerenciar documentos do iCloud no diretório Documentos do "contêiner de onipresença" fora do seu aplicativo por meio de Configurações; eles podem visualizar a lista de arquivos e deslizar para excluir. O código do aplicativo deve ser capaz de lidar com a situação em que os documentos são excluídos pelo usuário. Não armazene dados internos do aplicativo no diretório Documentos .

Gerenciando o fluxo de trabalho de documentos do iCloud

Os usuários também receberão avisos diferentes quando tentarem remover um aplicativo habilitado para iCloud de seu dispositivo, para informá-los sobre o status dos documentos do iCloud relacionados a esse aplicativo.

A captura de tela mostra um aviso para Atualizações de Documentos Pendentes.

A captura de tela mostra um aviso para Excluir i Cloud.

Backup do iCloud

Embora o backup no iCloud não seja um recurso acessado diretamente pelos desenvolvedores, a maneira como você projeta seu aplicativo pode afetar a experiência do usuário. A Apple fornece diretrizes de armazenamento de dados do iOS para os desenvolvedores seguirem em seus aplicativos iOS.

A consideração mais importante é se seu aplicativo armazena arquivos grandes que não são gerados pelo usuário (por exemplo, um aplicativo leitor de revista que armazena mais de cem megabytes de conteúdo por edição). A Apple prefere que você não armazene esse tipo de dados onde eles serão copiados para o iCloud e preencha desnecessariamente a cota do iCloud do usuário.

Aplicativos que armazenam grandes quantidades de dados como esse devem armazená-los em um dos diretórios de usuários que não é feito backup (por exemplo. Caches ou tmp) ou use NSFileManager.SetSkipBackupAttribute para aplicar um sinalizador a esses arquivos para que o iCloud os ignore durante as operações de backup.

Resumo

Este artigo apresentou o novo recurso do iCloud incluído no iOS 5. Ele examinou as etapas necessárias para configurar seu projeto para usar o iCloud e, em seguida, forneceu exemplos de como implementar os recursos do iCloud.

O exemplo de armazenamento de chave-valor demonstrou como o iCloud pode ser usado para armazenar uma pequena quantidade de dados semelhante à maneira como NSUserPreferences são armazenados. O exemplo do UIDocument mostrou como dados mais complexos podem ser armazenados e sincronizados em vários dispositivos via iCloud.

Por fim, incluiu uma breve discussão sobre como a adição do Backup do iCloud deve influenciar o design do aplicativo.