Seletor de documentos no Xamarin.iOS
O Seletor de Documentos permite que documentos sejam compartilhados entre aplicativos. Esses documentos podem ser armazenados no iCloud ou em um diretório de aplicativo diferente. Os documentos são compartilhados por meio do conjunto de Extensões do Provedor de Documentos que o usuário instalou em seu dispositivo.
Devido à dificuldade de manter os documentos sincronizados entre aplicativos e a nuvem, eles introduzem uma certa quantidade de complexidade necessária.
Requisitos
O seguinte é necessário para concluir as etapas apresentadas neste artigo:
- Xcode 7 e iOS 8 ou mais recente – As APIs Xcode 7 e iOS 8 ou mais recentes da Apple precisam ser instaladas e configuradas no computador do desenvolvedor.
- Visual Studio ou Visual Studio para Mac – A versão mais recente do Visual Studio para Mac deve ser instalada.
- Dispositivo iOS – Um dispositivo iOS com iOS 8 ou superior.
Alterações no iCloud
Para implementar os novos recursos do Seletor de Documentos, as seguintes alterações foram feitas no Serviço iCloud da Apple:
- O Daemon do iCloud foi completamente reescrito usando o CloudKit.
- Os recursos existentes do iCloud foram renomeados para iCloud Drive.
- O suporte para o sistema operacional Microsoft Windows foi adicionado ao iCloud.
- Foi adicionada uma pasta do iCloud no Mac OS Finder.
- Os dispositivos iOS podem acessar o conteúdo da pasta do Mac OS iCloud.
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.
O que é um documento?
Ao se referir a um documento no iCloud, ele é uma entidade única e autônoma e deve ser percebido como tal pelo usuário. Um usuário pode desejar modificar o documento ou compartilhá-lo com outros usuários (usando e-mail, por exemplo).
Existem vários tipos de arquivos que o usuário reconhecerá imediatamente como Documentos, como arquivos do Pages, Keynote ou Numbers. No entanto, o iCloud não se limita a este conceito. Por exemplo, o estado de um jogo (como uma partida de xadrez) pode ser tratado como um documento e armazenado no iCloud. Esse arquivo pode ser passado entre os dispositivos de um usuário e permitir que ele pegue um jogo de onde parou em um dispositivo diferente.
Lidando com documentos
Antes de se aprofundar no código necessário para usar o Seletor de Documentos com o Xamarin, este artigo abordará as práticas recomendadas para trabalhar com Documentos do iCloud e várias das modificações feitas nas APIs existentes necessárias para oferecer suporte ao Seletor de Documentos.
Usando a coordenação de arquivos
Como um arquivo pode ser modificado de vários locais diferentes, a coordenação deve ser usada para evitar a perda de dados.
Vamos dar uma olhada na ilustração acima:
- Um dispositivo iOS usando coordenação de arquivos cria um novo documento e o salva na pasta do iCloud.
- O iCloud salva o arquivo modificado na nuvem para distribuição a todos os dispositivos.
- Um Mac anexado vê o arquivo modificado na Pasta do iCloud e usa a Coordenação de Arquivos para copiar as alterações no arquivo.
- Um dispositivo que não usa a Coordenação de Arquivos faz uma alteração no arquivo e o salva na Pasta do iCloud. Essas alterações são replicadas instantaneamente para os outros dispositivos.
Suponha que o dispositivo iOS original ou o Mac estava editando o arquivo, agora suas alterações são perdidas e substituídas pela versão do arquivo do dispositivo não coordenado. Para evitar a perda de dados, a Coordenação de Arquivos é imprescindível ao trabalhar com documentos baseados em nuvem.
Usando UIDocument
UIDocument
torna as coisas simples (ou NSDocument
no macOS) fazendo todo o trabalho pesado para o desenvolvedor. Ele fornece coordenação de arquivos integrada com filas em segundo plano para evitar o bloqueio da interface do usuário do aplicativo.
UIDocument
expõe várias APIs de alto nível que facilitam o esforço de desenvolvimento de um aplicativo Xamarin para qualquer finalidade que o desenvolvedor exija.
O código a seguir cria uma subclasse de UIDocument
para implementar um documento genérico baseado em texto que pode ser usado para armazenar e recuperar texto do iCloud:
using System;
using Foundation;
using UIKit;
namespace DocPicker
{
public class GenericTextDocument : UIDocument
{
#region Private Variable Storage
private NSString _dataModel;
#endregion
#region Computed Properties
public string Contents {
get { return _dataModel.ToString (); }
set { _dataModel = new NSString(value); }
}
#endregion
#region Constructors
public GenericTextDocument (NSUrl url) : base (url)
{
// Set the default document text
this.Contents = "";
}
public GenericTextDocument (NSUrl url, string contents) : base (url)
{
// Set the default document text
this.Contents = contents;
}
#endregion
#region Override Methods
public override bool LoadFromContents (NSObject contents, string typeName, out NSError outError)
{
// Clear the error state
outError = null;
// Were any contents passed to the document?
if (contents != null) {
_dataModel = NSString.FromData( (NSData)contents, NSStringEncoding.UTF8 );
}
// Inform caller that the document has been modified
RaiseDocumentModified (this);
// Return success
return true;
}
public override NSObject ContentsForType (string typeName, out NSError outError)
{
// Clear the error state
outError = null;
// Convert the contents to a NSData object and return it
NSData docData = _dataModel.Encode(NSStringEncoding.UTF8);
return docData;
}
#endregion
#region Events
public delegate void DocumentModifiedDelegate(GenericTextDocument document);
public event DocumentModifiedDelegate DocumentModified;
internal void RaiseDocumentModified(GenericTextDocument document) {
// Inform caller
if (this.DocumentModified != null) {
this.DocumentModified (document);
}
}
#endregion
}
}
A GenericTextDocument
classe apresentada acima será usada ao longo deste artigo ao trabalhar com o Seletor de Documentos e documentos externos em um aplicativo Xamarin.iOS 8.
Coordenação de arquivos assíncronos
O iOS 8 fornece vários novos recursos de Coordenação de Arquivos Assíncronos por meio das novas APIs de Coordenação de Arquivos. Antes do iOS 8, todas as APIs de coordenação de arquivos existentes eram totalmente síncronas. Isso significava que o desenvolvedor era responsável por implementar sua própria fila em segundo plano para impedir que a Coordenação de Arquivos bloqueasse a interface do usuário do aplicativo.
A nova NSFileAccessIntent
classe contém uma URL apontando para o arquivo e várias opções para controlar o tipo de coordenação necessária. O código a seguir demonstra mover um arquivo de um local para outro usando intenções:
// Get source options
var srcURL = NSUrl.FromFilename ("FromFile.txt");
var srcIntent = NSFileAccessIntent.CreateReadingIntent (srcURL, NSFileCoordinatorReadingOptions.ForUploading);
// Get destination options
var dstURL = NSUrl.FromFilename ("ToFile.txt");
var dstIntent = NSFileAccessIntent.CreateReadingIntent (dstURL, NSFileCoordinatorReadingOptions.ForUploading);
// Create an array
var intents = new NSFileAccessIntent[] {
srcIntent,
dstIntent
};
// Initialize a file coordination with intents
var queue = new NSOperationQueue ();
var fileCoordinator = new NSFileCoordinator ();
fileCoordinator.CoordinateAccess (intents, queue, (err) => {
// Was there an error?
if (err!=null) {
Console.WriteLine("Error: {0}",err.LocalizedDescription);
}
});
Descobrindo e listando documentos
A maneira de descobrir e listar documentos é usando as APIs existentes NSMetadataQuery
. Esta seção abordará novos recursos adicionados que tornam o NSMetadataQuery
trabalho com documentos ainda mais fácil do que antes.
Comportamento existente
Antes do iOS 8, era lento para pegar alterações de arquivos locais, NSMetadataQuery
tais como: exclui, cria e renomeia.
No diagrama acima:
- Para arquivos que já existem no Contêiner de Aplicativos,
NSMetadataQuery
tem registros existentesNSMetadata
pré-criados e spooled para que eles fiquem instantaneamente disponíveis para o aplicativo. - O aplicativo cria um novo arquivo no contêiner de aplicativo.
- Há um atraso antes
NSMetadataQuery
de ver a modificação no contêiner de aplicativo e cria o registro necessárioNSMetadata
.
Devido ao atraso na criação do registro, o aplicativo teve que ter duas fontes de dados abertas: uma para alterações de NSMetadata
arquivo local e outra para alterações baseadas em nuvem.
Costura
No iOS 8, NSMetadataQuery
é mais fácil usar diretamente com um novo recurso chamado Stitching:
Usando costura no diagrama acima:
- Como antes, para arquivos que já existem no Contêiner de Aplicativos,
NSMetadataQuery
tem registros existentesNSMetadata
pré-criados e spooled. - O aplicativo cria um novo arquivo no contêiner de aplicativo usando a coordenação de arquivos.
- Um gancho no contêiner de aplicativo vê a modificação e chama
NSMetadataQuery
para criar o registro necessárioNSMetadata
. - O
NSMetadata
registro é criado diretamente após o arquivo e é disponibilizado para o aplicativo.
Usando o Stitching, o aplicativo não precisa mais abrir uma fonte de dados para monitorar alterações de arquivo locais e baseadas em nuvem. Agora, o aplicativo pode contar diretamente NSMetadataQuery
.
Importante
A costura só funciona se o Aplicativo estiver usando a Coordenação de Arquivos, conforme apresentado na seção acima. Se a Coordenação de Arquivos não estiver sendo usada, as APIs terão como padrão o comportamento existente anterior ao iOS 8.
Novos recursos de metadados do iOS 8
Os seguintes novos recursos foram adicionados ao NSMetadataQuery
iOS 8:
NSMetatadataQuery
agora pode listar documentos não locais armazenados na nuvem.- Novas APIs foram adicionadas para acessar informações de metadados nos documentos baseados em nuvem.
- Há uma nova
NSUrl_PromisedItems
API que irá acessar os atributos de arquivo de arquivos que podem ou não ter seu conteúdo disponível localmente. - Use o
GetPromisedItemResourceValue
método para obter informações sobre um determinado arquivo ou use oGetPromisedItemResourceValues
método para obter informações sobre mais de um arquivo ao mesmo tempo.
Dois novos sinalizadores de coordenação de arquivos foram adicionados para lidar com metadados:
NSFileCoordinatorReadImmediatelyAvailableMetadataOnly
NSFileCoordinatorWriteContentIndependentMetadataOnly
Com os sinalizadores acima, o conteúdo do arquivo Document não precisa estar disponível localmente para que eles sejam usados.
O segmento de código a seguir mostra como usar NSMetadataQuery
para consultar a existência de um arquivo específico e compilar o arquivo se ele não existir:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Foundation;
using UIKit;
using ObjCRuntime;
using System.IO;
#region Static Properties
public const string TestFilename = "test.txt";
#endregion
#region Computed Properties
public bool HasiCloud { get; set; }
public bool CheckingForiCloud { get; set; }
public NSUrl iCloudUrl { get; set; }
public GenericTextDocument Document { get; set; }
public NSMetadataQuery Query { get; set; }
#endregion
#region Private Methods
private void FindDocument () {
Console.WriteLine ("Finding Document...");
// Create a new query and set it's scope
Query = new NSMetadataQuery();
Query.SearchScopes = new NSObject [] {
NSMetadataQuery.UbiquitousDocumentsScope,
NSMetadataQuery.UbiquitousDataScope,
NSMetadataQuery.AccessibleUbiquitousExternalDocumentsScope
};
// Build a predicate to locate the file by name and attach it to the query
var pred = NSPredicate.FromFormat ("%K == %@"
, new NSObject[] {
NSMetadataQuery.ItemFSNameKey
, new NSString(TestFilename)});
Query.Predicate = pred;
// Register a notification for when the query returns
NSNotificationCenter.DefaultCenter.AddObserver (this,
new Selector("queryDidFinishGathering:"), NSMetadataQuery.DidFinishGatheringNotification,
Query);
// Start looking for the file
Query.StartQuery ();
Console.WriteLine ("Querying: {0}", Query.IsGathering);
}
[Export("queryDidFinishGathering:")]
public void DidFinishGathering (NSNotification notification) {
Console.WriteLine ("Finish Gathering Documents.");
// Access the query and stop it from running
var query = (NSMetadataQuery)notification.Object;
query.DisableUpdates();
query.StopQuery();
// Release the notification
NSNotificationCenter.DefaultCenter.RemoveObserver (this
, NSMetadataQuery.DidFinishGatheringNotification
, query);
// Load the document that the query returned
LoadDocument(query);
}
private void LoadDocument (NSMetadataQuery query) {
Console.WriteLine ("Loading Document...");
// Take action based on the returned record count
switch (query.ResultCount) {
case 0:
// Create a new document
CreateNewDocument ();
break;
case 1:
// Gain access to the url and create a new document from
// that instance
NSMetadataItem item = (NSMetadataItem)query.ResultAtIndex (0);
var url = (NSUrl)item.ValueForAttribute (NSMetadataQuery.ItemURLKey);
// Load the document
OpenDocument (url);
break;
default:
// There has been an issue
Console.WriteLine ("Issue: More than one document found...");
break;
}
}
#endregion
#region Public Methods
public void OpenDocument(NSUrl url) {
Console.WriteLine ("Attempting to open: {0}", url);
Document = new GenericTextDocument (url);
// Open the document
Document.Open ( (success) => {
if (success) {
Console.WriteLine ("Document Opened");
} else
Console.WriteLine ("Failed to Open Document");
});
// Inform caller
RaiseDocumentLoaded (Document);
}
public void CreateNewDocument() {
// Create path to new file
// var docsFolder = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
var docsFolder = Path.Combine(iCloudUrl.Path, "Documents");
var docPath = Path.Combine (docsFolder, TestFilename);
var ubiq = new NSUrl (docPath, false);
// Create new document at path
Console.WriteLine ("Creating Document at:" + ubiq.AbsoluteString);
Document = new GenericTextDocument (ubiq);
// Set the default value
Document.Contents = "(default value)";
// Save document to path
Document.Save (Document.FileUrl, UIDocumentSaveOperation.ForCreating, (saveSuccess) => {
Console.WriteLine ("Save completion:" + saveSuccess);
if (saveSuccess) {
Console.WriteLine ("Document Saved");
} else {
Console.WriteLine ("Unable to Save Document");
}
});
// Inform caller
RaiseDocumentLoaded (Document);
}
public bool SaveDocument() {
bool successful = false;
// Save document to path
Document.Save (Document.FileUrl, UIDocumentSaveOperation.ForOverwriting, (saveSuccess) => {
Console.WriteLine ("Save completion: " + saveSuccess);
if (saveSuccess) {
Console.WriteLine ("Document Saved");
successful = true;
} else {
Console.WriteLine ("Unable to Save Document");
successful=false;
}
});
// Return results
return successful;
}
#endregion
#region Events
public delegate void DocumentLoadedDelegate(GenericTextDocument document);
public event DocumentLoadedDelegate DocumentLoaded;
internal void RaiseDocumentLoaded(GenericTextDocument document) {
// Inform caller
if (this.DocumentLoaded != null) {
this.DocumentLoaded (document);
}
}
#endregion
Miniaturas de documentos
A Apple acha que a melhor experiência do usuário ao listar documentos para um aplicativo é usar visualizações. Isso dá contexto aos usuários finais, para que eles possam identificar rapidamente o documento com o qual desejam trabalhar.
Antes do iOS 8, a exibição de visualizações de documentos exigia uma implementação personalizada. Uma novidade no iOS 8 são os atributos do sistema de arquivos que permitem que o desenvolvedor trabalhe rapidamente com miniaturas de documentos.
Recuperando miniaturas de documentos
Ao chamar os métodos orGetPromisedItemResourceValues
, NSUrl_PromisedItems
a GetPromisedItemResourceValue
API, a NSUrlThumbnailDictionary
, é retornada. A única chave atualmente neste dicionário é o NSThumbnial1024X1024SizeKey
e sua correspondência UIImage
.
Salvando miniaturas de documentos
A maneira mais fácil de salvar uma miniatura é usando UIDocument
o . Ao chamar o GetFileAttributesToWrite
método do UIDocument
e definir a miniatura, ele será salvo automaticamente quando o arquivo Document estiver. O Daemon do iCloud verá essa alteração e a propagará para o iCloud. No Mac OS X, as miniaturas são geradas automaticamente para o desenvolvedor pelo plug-in Quick Look.
Com as noções básicas de trabalho com documentos baseados no iCloud, juntamente com as modificações na API existente, estamos prontos para implementar o Controlador de Exibição do Seletor de Documentos em um aplicativo móvel Xamarin iOS 8.
Ativando o iCloud no Xamarin
Para que o Seletor de Documentos possa ser usado em um aplicativo Xamarin.iOS, o suporte ao iCloud precisa ser ativado tanto em seu aplicativo quanto via Apple.
As etapas a seguir explicam o processo de provisionamento para o iCloud.
- Crie um contêiner do iCloud.
- Crie uma ID de Aplicativo que contenha o Serviço de Aplicativo do iCloud.
- Crie um perfil de provisionamento que inclua essa ID do aplicativo.
O guia Trabalhando com recursos percorre as duas primeiras etapas. Para criar um perfil de provisionamento, siga as etapas no guia Perfil de provisionamento .
As etapas a seguir explicam o processo de configuração do aplicativo para o iCloud:
Faça o seguinte:
Abra o projeto no Visual Studio para Mac ou Visual Studio.
No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Opções.
Na caixa de diálogo Opções, selecione Aplicativo iOS, verifique se o Identificador de pacote corresponde ao que foi definido na ID do aplicativo criada acima para o aplicativo.
Selecione Assinatura de pacote do iOS, selecione a Identidade do desenvolvedor e o perfil de provisionamento criados acima.
Clique no botão OK para salvar as alterações e fechar a caixa de diálogo.
Clique com o botão direito do mouse
Entitlements.plist
no Gerenciador de Soluções para abri-lo no editor.Importante
No Visual Studio, talvez seja necessário abrir o editor de Direitos clicando com o botão direito do mouse nele, selecionando Abrir com... e selecionando Editor de Lista de Propriedades
Marque Ativar iCloud , Documentos do iCloud, Armazenamento de chave-valor e CloudKit .
Verifique se o contêiner existe para o aplicativo (conforme criado acima). Exemplo:
iCloud.com.your-company.AppName
Salve as alterações no arquivo.
Para obter mais informações sobre Direitos, consulte o guia Trabalhando com Direitos .
Com a configuração acima, o aplicativo agora pode usar documentos baseados em nuvem e o novo Controlador de Exibição do Seletor de Documentos.
Código de instalação comum
Antes de começar a usar o Controlador de Exibição do Seletor de Documentos, é necessário algum código de instalação padrão. Comece modificando o arquivo do AppDelegate.cs
aplicativo e faça com que ele tenha a seguinte aparência:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Foundation;
using UIKit;
using ObjCRuntime;
using System.IO;
namespace DocPicker
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
#region Static Properties
public const string TestFilename = "test.txt";
#endregion
#region Computed Properties
public override UIWindow Window { get; set; }
public bool HasiCloud { get; set; }
public bool CheckingForiCloud { get; set; }
public NSUrl iCloudUrl { get; set; }
public GenericTextDocument Document { get; set; }
public NSMetadataQuery Query { get; set; }
public NSData Bookmark { get; set; }
#endregion
#region Private Methods
private void FindDocument () {
Console.WriteLine ("Finding Document...");
// Create a new query and set it's scope
Query = new NSMetadataQuery();
Query.SearchScopes = new NSObject [] {
NSMetadataQuery.UbiquitousDocumentsScope,
NSMetadataQuery.UbiquitousDataScope,
NSMetadataQuery.AccessibleUbiquitousExternalDocumentsScope
};
// Build a predicate to locate the file by name and attach it to the query
var pred = NSPredicate.FromFormat ("%K == %@",
new NSObject[] {NSMetadataQuery.ItemFSNameKey
, new NSString(TestFilename)});
Query.Predicate = pred;
// Register a notification for when the query returns
NSNotificationCenter.DefaultCenter.AddObserver (this
, new Selector("queryDidFinishGathering:")
, NSMetadataQuery.DidFinishGatheringNotification
, Query);
// Start looking for the file
Query.StartQuery ();
Console.WriteLine ("Querying: {0}", Query.IsGathering);
}
[Export("queryDidFinishGathering:")]
public void DidFinishGathering (NSNotification notification) {
Console.WriteLine ("Finish Gathering Documents.");
// Access the query and stop it from running
var query = (NSMetadataQuery)notification.Object;
query.DisableUpdates();
query.StopQuery();
// Release the notification
NSNotificationCenter.DefaultCenter.RemoveObserver (this
, NSMetadataQuery.DidFinishGatheringNotification
, query);
// Load the document that the query returned
LoadDocument(query);
}
private void LoadDocument (NSMetadataQuery query) {
Console.WriteLine ("Loading Document...");
// Take action based on the returned record count
switch (query.ResultCount) {
case 0:
// Create a new document
CreateNewDocument ();
break;
case 1:
// Gain access to the url and create a new document from
// that instance
NSMetadataItem item = (NSMetadataItem)query.ResultAtIndex (0);
var url = (NSUrl)item.ValueForAttribute (NSMetadataQuery.ItemURLKey);
// Load the document
OpenDocument (url);
break;
default:
// There has been an issue
Console.WriteLine ("Issue: More than one document found...");
break;
}
}
#endregion
#region Public Methods
public void OpenDocument(NSUrl url) {
Console.WriteLine ("Attempting to open: {0}", url);
Document = new GenericTextDocument (url);
// Open the document
Document.Open ( (success) => {
if (success) {
Console.WriteLine ("Document Opened");
} else
Console.WriteLine ("Failed to Open Document");
});
// Inform caller
RaiseDocumentLoaded (Document);
}
public void CreateNewDocument() {
// Create path to new file
// var docsFolder = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
var docsFolder = Path.Combine(iCloudUrl.Path, "Documents");
var docPath = Path.Combine (docsFolder, TestFilename);
var ubiq = new NSUrl (docPath, false);
// Create new document at path
Console.WriteLine ("Creating Document at:" + ubiq.AbsoluteString);
Document = new GenericTextDocument (ubiq);
// Set the default value
Document.Contents = "(default value)";
// Save document to path
Document.Save (Document.FileUrl, UIDocumentSaveOperation.ForCreating, (saveSuccess) => {
Console.WriteLine ("Save completion:" + saveSuccess);
if (saveSuccess) {
Console.WriteLine ("Document Saved");
} else {
Console.WriteLine ("Unable to Save Document");
}
});
// Inform caller
RaiseDocumentLoaded (Document);
}
/// <summary>
/// Saves the document.
/// </summary>
/// <returns><c>true</c>, if document was saved, <c>false</c> otherwise.</returns>
public bool SaveDocument() {
bool successful = false;
// Save document to path
Document.Save (Document.FileUrl, UIDocumentSaveOperation.ForOverwriting, (saveSuccess) => {
Console.WriteLine ("Save completion: " + saveSuccess);
if (saveSuccess) {
Console.WriteLine ("Document Saved");
successful = true;
} else {
Console.WriteLine ("Unable to Save Document");
successful=false;
}
});
// Return results
return successful;
}
#endregion
#region Override Methods
public override void FinishedLaunching (UIApplication application)
{
// Start a new thread to check and see if the user has iCloud
// enabled.
new Thread(new ThreadStart(() => {
// Inform caller that we are checking for iCloud
CheckingForiCloud = true;
// Checks to see if the user of this device has iCloud
// enabled
var uburl = NSFileManager.DefaultManager.GetUrlForUbiquityContainer(null);
// Connected to iCloud?
if (uburl == null)
{
// No, inform caller
HasiCloud = false;
iCloudUrl =null;
Console.WriteLine("Unable to connect to iCloud");
InvokeOnMainThread(()=>{
var okAlertController = UIAlertController.Create ("iCloud Not Available", "Developer, please check your Entitlements.plist, Bundle ID and Provisioning Profiles.", UIAlertControllerStyle.Alert);
okAlertController.AddAction (UIAlertAction.Create ("Ok", UIAlertActionStyle.Default, null));
Window.RootViewController.PresentViewController (okAlertController, true, null);
});
}
else
{
// Yes, inform caller and save location the Application Container
HasiCloud = true;
iCloudUrl = uburl;
Console.WriteLine("Connected to iCloud");
// If we have made the connection with iCloud, start looking for documents
InvokeOnMainThread(()=>{
// Search for the default document
FindDocument ();
});
}
// Inform caller that we are no longer looking for iCloud
CheckingForiCloud = false;
})).Start();
}
// This method is invoked when the application is about to move from active to inactive state.
// OpenGL applications should use this method to pause.
public override void OnResignActivation (UIApplication application)
{
}
// This method should be used to release shared resources and it should store the application state.
// If your application supports background execution this method is called instead of WillTerminate
// when the user quits.
public override void DidEnterBackground (UIApplication application)
{
// Trap all errors
try {
// Values to include in the bookmark packet
var resources = new string[] {
NSUrl.FileSecurityKey,
NSUrl.ContentModificationDateKey,
NSUrl.FileResourceIdentifierKey,
NSUrl.FileResourceTypeKey,
NSUrl.LocalizedNameKey
};
// Create the bookmark
NSError err;
Bookmark = Document.FileUrl.CreateBookmarkData (NSUrlBookmarkCreationOptions.WithSecurityScope, resources, iCloudUrl, out err);
// Was there an error?
if (err != null) {
// Yes, report it
Console.WriteLine ("Error Creating Bookmark: {0}", err.LocalizedDescription);
}
}
catch (Exception e) {
// Report error
Console.WriteLine ("Error: {0}", e.Message);
}
}
// This method is called as part of the transition from background to active state.
public override void WillEnterForeground (UIApplication application)
{
// Is there any bookmark data?
if (Bookmark != null) {
// Trap all errors
try {
// Yes, attempt to restore it
bool isBookmarkStale;
NSError err;
var srcUrl = new NSUrl (Bookmark, NSUrlBookmarkResolutionOptions.WithSecurityScope, iCloudUrl, out isBookmarkStale, out err);
// Was there an error?
if (err != null) {
// Yes, report it
Console.WriteLine ("Error Loading Bookmark: {0}", err.LocalizedDescription);
} else {
// Load document from bookmark
OpenDocument (srcUrl);
}
}
catch (Exception e) {
// Report error
Console.WriteLine ("Error: {0}", e.Message);
}
}
}
// This method is called when the application is about to terminate. Save data, if needed.
public override void WillTerminate (UIApplication application)
{
}
#endregion
#region Events
public delegate void DocumentLoadedDelegate(GenericTextDocument document);
public event DocumentLoadedDelegate DocumentLoaded;
internal void RaiseDocumentLoaded(GenericTextDocument document) {
// Inform caller
if (this.DocumentLoaded != null) {
this.DocumentLoaded (document);
}
}
#endregion
}
}
Importante
O código acima inclui o código da seção Descobrindo e listando documentos acima. Ele é apresentado aqui na íntegra, como apareceria em um pedido real. Para simplificar, este exemplo funciona apenas com um único arquivo embutido em código (test.txt
).
O código acima expõe vários atalhos do iCloud Drive para torná-los mais fáceis de trabalhar no restante do aplicativo.
Em seguida, adicione o seguinte código a qualquer modo de exibição ou contêiner de exibição que usará o Seletor de Documentos ou trabalhará com documentos baseados em nuvem:
using CloudKit;
...
#region Computed Properties
/// <summary>
/// Returns the delegate of the current running application
/// </summary>
/// <value>The this app.</value>
public AppDelegate ThisApp {
get { return (AppDelegate)UIApplication.SharedApplication.Delegate; }
}
#endregion
Isso adiciona um atalho para acessar os atalhos do AppDelegate
iCloud criados acima.
Com esse código em vigor, vamos dar uma olhada na implementação do Document Picker View Controller em um aplicativo Xamarin iOS 8.
Usando o controlador de exibição do seletor de documentos
Antes do iOS 8, era muito difícil acessar documentos de outro aplicativo porque não havia como descobrir documentos fora do aplicativo de dentro do aplicativo.
Comportamento existente
Vamos dar uma olhada no acesso a um documento externo anterior ao iOS 8:
- Primeiro, o usuário teria que abrir o aplicativo que originalmente criou o documento.
- O documento é selecionado e o
UIDocumentInteractionController
é usado para enviar o documento para o novo aplicativo. - Finalmente, uma cópia do documento original é colocada no contêiner do novo aplicativo.
A partir daí, o documento fica disponível para o segundo aplicativo ser aberto e editado.
Descobrindo documentos fora do contêiner de um aplicativo
No iOS 8, um aplicativo é capaz de acessar documentos fora de seu próprio contêiner de aplicativos com facilidade:
Usando o novo Seletor de Documentos do iCloud ( UIDocumentPickerViewController
), um aplicativo iOS pode descobrir e acessar diretamente fora de seu Contêiner de Aplicativos. O UIDocumentPickerViewController
fornece um mecanismo para que o usuário conceda acesso e edite os documentos descobertos por meio de permissões.
Um aplicativo deve optar por fazer com que seus Documentos apareçam no Seletor de Documentos do iCloud e estejam disponíveis para que outros aplicativos os descubram e trabalhem com eles. Para que um aplicativo Xamarin iOS 8 compartilhe seu Contêiner de Aplicativos, edite-o Info.plist
em um editor de texto padrão e adicione as duas linhas a seguir à parte inferior do dicionário (entre as <dict>...</dict>
tags):
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
O UIDocumentPickerViewController
fornece uma ótima nova interface do usuário que permite ao usuário escolher documentos. Para exibir o Controlador de Exibição do Seletor de Documentos em um aplicativo Xamarin iOS 8, faça o seguinte:
using MobileCoreServices;
...
// Allow the Document picker to select a range of document types
var allowedUTIs = new string[] {
UTType.UTF8PlainText,
UTType.PlainText,
UTType.RTF,
UTType.PNG,
UTType.Text,
UTType.PDF,
UTType.Image
};
// Display the picker
//var picker = new UIDocumentPickerViewController (allowedUTIs, UIDocumentPickerMode.Open);
var pickerMenu = new UIDocumentMenuViewController(allowedUTIs, UIDocumentPickerMode.Open);
pickerMenu.DidPickDocumentPicker += (sender, args) => {
// Wireup Document Picker
args.DocumentPicker.DidPickDocument += (sndr, pArgs) => {
// IMPORTANT! You must lock the security scope before you can
// access this file
var securityEnabled = pArgs.Url.StartAccessingSecurityScopedResource();
// Open the document
ThisApp.OpenDocument(pArgs.Url);
// IMPORTANT! You must release the security lock established
// above.
pArgs.Url.StopAccessingSecurityScopedResource();
};
// Display the document picker
PresentViewController(args.DocumentPicker,true,null);
};
pickerMenu.ModalPresentationStyle = UIModalPresentationStyle.Popover;
PresentViewController(pickerMenu,true,null);
UIPopoverPresentationController presentationPopover = pickerMenu.PopoverPresentationController;
if (presentationPopover!=null) {
presentationPopover.SourceView = this.View;
presentationPopover.PermittedArrowDirections = UIPopoverArrowDirection.Down;
presentationPopover.SourceRect = ((UIButton)s).Frame;
}
Importante
O desenvolvedor deve chamar o StartAccessingSecurityScopedResource
método do antes que NSUrl
um documento externo possa ser acessado. O StopAccessingSecurityScopedResource
método deve ser chamado para liberar o bloqueio de segurança assim que o documento for carregado.
Saída de exemplo
Aqui está um exemplo de como o código acima exibiria um Seletor de Documentos quando executado em um dispositivo iPhone:
O usuário inicia o aplicativo e a interface principal é exibida:
O usuário toca no botão Ação na parte superior da tela e é solicitado a selecionar um Provedor de Documentos na lista de provedores disponíveis:
O Controlador de Exibição do Seletor de Documentos é exibido para o Provedor de Documentos selecionado:
O usuário toca em uma Pasta de Documentos para exibir seu conteúdo:
O usuário seleciona um documento e o seletor de documentos é fechado.
A interface principal é reexibida, o documento é carregado do contêiner externo e seu conteúdo exibido.
A exibição real do Controlador de Exibição do Seletor de Documentos depende dos Provedores de Documentos que o usuário instalou no dispositivo e do Modo de Seletor de Documentos que foi implementado. O exemplo acima está usando o Modo Aberto, os outros tipos de modo serão discutidos em detalhes abaixo.
Gerenciando documentos externos
Como discutido acima, antes do iOS 8, um aplicativo só podia acessar documentos que faziam parte de seu contêiner de aplicativos. No iOS 8, um aplicativo pode acessar documentos de fontes externas:
Quando o usuário seleciona um documento de uma fonte externa, um documento de referência é gravado no contêiner de aplicativo que aponta para o documento original.
Para ajudar a adicionar essa nova capacidade aos aplicativos existentes, vários novos recursos foram adicionados à NSMetadataQuery
API. Normalmente, um aplicativo usa o escopo de documento ubíquo para listar documentos que vivem em seu contêiner de aplicativo. Usando esse escopo, somente os documentos dentro do contêiner de aplicativo continuarão a ser exibidos.
O uso do novo Escopo de Documento Externo Ubíquo retornará Documentos que moram fora do Contêiner de Aplicativo e retornará os metadados para eles. O NSMetadataItemUrlKey
apontará para a URL onde o documento está realmente localizado.
Às vezes, um aplicativo não quer trabalhar com os documentos que estão sendo apontados por referência. Em vez disso, o aplicativo deseja trabalhar diretamente com o Documento de Referência. Por exemplo, o aplicativo pode querer exibir o documento na pasta do aplicativo na interface do usuário ou permitir que o usuário mova as referências dentro de uma pasta.
No iOS 8, um novo NSMetadataItemUrlInLocalContainerKey
foi fornecido para acessar o Documento de Referência diretamente. Essa chave aponta para a referência real ao documento externo em um contêiner de aplicativo.
O NSMetadataUbiquitousItemIsExternalDocumentKey
é usado para testar se um documento é ou não externo ao contêiner de um aplicativo. O NSMetadataUbiquitousItemContainerDisplayNameKey
é usado para acessar o nome do contêiner que está hospedando a cópia original de um documento externo.
Por que as referências de documentos são obrigatórias
A principal razão pela qual o iOS 8 usa referências para acessar documentos externos é a segurança. Nenhum aplicativo tem acesso ao Contêiner de qualquer outro aplicativo. Somente o Seletor de Documentos pode fazer isso, porque está ficando fora do processo e tem acesso em todo o sistema.
A única maneira de acessar um documento fora do Contêiner de Aplicativo é usando o Seletor de Documentos e se a URL retornada pelo seletor for Escopo de Segurança. A URL com escopo de segurança contém apenas informações suficientes para selecionar o documento, juntamente com os direitos de escopo necessários para conceder a um aplicativo acesso ao documento.
É importante observar que, se a URL com escopo de segurança fosse serializada em uma cadeia de caracteres e, em seguida, desserializada, as informações de segurança seriam perdidas e o arquivo ficaria inacessível a partir da URL. O recurso Referência de Documento fornece um mecanismo para voltar aos arquivos apontados por essas URLs.
Portanto, se o aplicativo adquirir um NSUrl
de um dos Documentos de Referência, ele já tem o escopo de segurança anexado e pode ser usado para acessar o arquivo. Por esse motivo, é altamente recomendável que o desenvolvedor use UIDocument
porque ele lida com todas essas informações e processos para eles.
Usar indicadores
Nem sempre é viável enumerar os Documentos de um aplicativo para voltar a um Documento específico, por exemplo, ao fazer a restauração de estado. O iOS 8 fornece um mecanismo para criar Favoritos que visam diretamente um determinado Documento.
O código a seguir criará um Bookmark a partir de uma UIDocument
propriedade 's FileUrl
:
// Trap all errors
try {
// Values to include in the bookmark packet
var resources = new string[] {
NSUrl.FileSecurityKey,
NSUrl.ContentModificationDateKey,
NSUrl.FileResourceIdentifierKey,
NSUrl.FileResourceTypeKey,
NSUrl.LocalizedNameKey
};
// Create the bookmark
NSError err;
Bookmark = Document.FileUrl.CreateBookmarkData (NSUrlBookmarkCreationOptions.WithSecurityScope, resources, iCloudUrl, out err);
// Was there an error?
if (err != null) {
// Yes, report it
Console.WriteLine ("Error Creating Bookmark: {0}", err.LocalizedDescription);
}
}
catch (Exception e) {
// Report error
Console.WriteLine ("Error: {0}", e.Message);
}
A API de Marcador existente é usada para criar um Marcador em relação a um existente NSUrl
que pode ser salvo e carregado para fornecer acesso direto a um arquivo externo. O código a seguir restaurará um indicador que foi criado acima:
if (Bookmark != null) {
// Trap all errors
try {
// Yes, attempt to restore it
bool isBookmarkStale;
NSError err;
var srcUrl = new NSUrl (Bookmark, NSUrlBookmarkResolutionOptions.WithSecurityScope, iCloudUrl, out isBookmarkStale, out err);
// Was there an error?
if (err != null) {
// Yes, report it
Console.WriteLine ("Error Loading Bookmark: {0}", err.LocalizedDescription);
} else {
// Load document from bookmark
OpenDocument (srcUrl);
}
}
catch (Exception e) {
// Report error
Console.WriteLine ("Error: {0}", e.Message);
}
}
Abrir vs. Modo de Importação e o Seletor de Documentos
O Controlador de Exibição do Seletor de Documentos apresenta dois modos diferentes de operação:
Modo Aberto – Nesse modo, quando o usuário seleciona um Documento externo, o Seletor de Documentos criará um Indicador com Escopo de Segurança no Contêiner de Aplicativo.
Modo de Importação – Nesse modo, quando o usuário seleciona um Documento externo, o Seletor de Documentos não cria um Indicador, mas copia o arquivo em um Local Temporário e fornece ao aplicativo acesso ao Documento neste local:
Quando o aplicativo é encerrado por qualquer motivo, o Local Temporário é esvaziado e o arquivo removido. Se o aplicativo precisar manter o acesso ao arquivo, ele deverá fazer uma cópia e colocá-lo em seu contêiner de aplicativo.
O Modo Aberto é útil quando o aplicativo deseja colaborar com outro aplicativo e compartilhar quaisquer alterações feitas no documento com esse aplicativo. O Modo de Importação é usado quando o aplicativo não deseja compartilhar suas modificações em um documento com outros aplicativos.
Tornando um documento externo
Como mencionado acima, um aplicativo iOS 8 não tem acesso a contêineres fora de seu próprio contêiner de aplicativo. O aplicativo pode gravar em seu próprio contêiner localmente ou em um local temporário e, em seguida, usar um modo de documento especial para mover o documento resultante fora do contêiner de aplicativo para um local escolhido pelo usuário.
Para mover um documento para um local externo, faça o seguinte:
- Primeiro, crie um novo documento em um local local ou temporário.
- Crie um
NSUrl
que aponte para o novo Documento. - Abra um novo Controlador de Exibição do Seletor de
MoveToService
Documentos e passe-oNSUrl
com o Modo de . - Depois que o usuário escolher um novo local, o documento será movido de seu local atual para o novo local.
- Um documento de referência será gravado no contêiner de aplicativo do aplicativo para que o arquivo ainda possa ser acessado pelo aplicativo de criação.
O código a seguir pode ser usado para mover um documento para um local externo: var picker = new UIDocumentPickerViewController (srcURL, UIDocumentPickerMode.MoveToService);
O Documento de Referência retornado pelo processo acima é exatamente o mesmo criado pelo Modo Aberto do Seletor de Documentos. No entanto, há momentos em que o aplicativo pode desejar mover um documento sem manter uma referência a ele.
Para mover um documento sem gerar uma referência, use o ExportToService
modo. Exemplo: var picker = new UIDocumentPickerViewController (srcURL, UIDocumentPickerMode.ExportToService);
Ao usar o ExportToService
modo, o documento é copiado para o contêiner externo e a cópia existente é deixada em seu local original.
Extensões do provedor de documentos
Com o iOS 8, a Apple quer que o usuário final possa acessar qualquer um de seus documentos baseados em nuvem, não importa onde eles realmente existam. Para atingir esse objetivo, o iOS 8 fornece um novo mecanismo de Extensão do Provedor de Documentos.
O que é uma extensão de provedor de documentos?
Simplificando, uma Extensão de Provedor de Documentos é uma maneira de um desenvolvedor, ou um terceiro, fornecer a um aplicativo armazenamento de documentos alternativo que pode ser acessado exatamente da mesma maneira que o local de armazenamento existente do iCloud.
O usuário pode selecionar um desses locais de armazenamento alternativos no Seletor de Documentos e pode usar exatamente os mesmos modos de acesso (Abrir, Importar, Mover ou Exportar) para trabalhar com arquivos nesse local.
Isso é implementado usando duas extensões diferentes:
- Extensão do Seletor de Documentos – Fornece uma
UIViewController
subclasse que fornece uma interface gráfica para o usuário escolher um documento de um local de armazenamento alternativo. Essa subclasse será exibida como parte do Controlador de Exibição do Seletor de Documentos. - File Provide Extension - Esta é uma extensão não-UI que lida com realmente fornecer o conteúdo dos arquivos. Essas extensões são fornecidas através da Coordenação de Arquivos (
NSFileCoordinator
). Este é outro caso importante em que a Coordenação de Arquivos é necessária.
O diagrama a seguir mostra o fluxo de dados típico ao trabalhar com extensões de provedor de documentos:
Ocorre o seguinte processo:
- O aplicativo apresenta um controlador de seletor de documentos para permitir que o usuário selecione um arquivo para trabalhar.
- O usuário seleciona um local de arquivo alternativo e a extensão personalizada
UIViewController
é chamada para exibir a interface do usuário. - O usuário seleciona um arquivo desse local e a URL é passada de volta para o Seletor de Documentos.
- O Seletor de Documentos seleciona a URL do arquivo e a retorna ao aplicativo para o usuário trabalhar.
- A URL é passada para o Coordenador de Arquivos para retornar o conteúdo dos arquivos ao aplicativo.
- O Coordenador de Arquivos chama a Extensão de Provedor de Arquivos personalizada para recuperar o arquivo.
- O conteúdo do arquivo é retornado ao Coordenador de Arquivos.
- O conteúdo do arquivo é retornado ao aplicativo.
Segurança e Favoritos
Esta seção examinará rapidamente como a segurança e o acesso persistente a arquivos por meio de Favoritos funcionam com as Extensões de Provedor de Documentos. Ao contrário do Provedor de Documentos do iCloud, que salva automaticamente a Segurança e os Marcadores no Contêiner de Aplicativos, as Extensões do Provedor de Documentos não o fazem porque não fazem parte do Sistema de Referência de Documentos.
Por exemplo: em uma configuração Enterprise que fornece seu próprio armazenamento de dados seguro em toda a empresa, os administradores não querem que informações corporativas confidenciais sejam acessadas ou processadas pelos Servidores iCloud públicos. Portanto, o sistema interno de referência de documento não pode ser usado.
O sistema Bookmark ainda pode ser usado e é responsabilidade da Extensão do Provedor de Arquivos processar corretamente uma URL marcada e retornar o conteúdo do Documento apontado por ela.
Para fins de segurança, o iOS 8 tem uma Camada de Isolamento que persiste as informações sobre qual aplicativo tem acesso a qual identificador dentro de qual Provedor de Arquivos. Deve-se notar que todo o acesso ao arquivo é controlado por essa camada de isolamento.
O diagrama a seguir mostra o fluxo de dados ao trabalhar com Marcadores e uma Extensão de Provedor de Documentos:
Ocorre o seguinte processo:
- O aplicativo está prestes a entrar em segundo plano e precisa manter seu estado. Ele chama
NSUrl
para criar um marcador para um arquivo no armazenamento alternativo. NSUrl
chama a Extensão do Provedor de Arquivos para obter uma URL persistente para o Documento.- A extensão do provedor de arquivos retorna a URL como uma cadeia de caracteres para o
NSUrl
. - O
NSUrl
agrupa a URL em um Marcador e a retorna ao aplicativo. - Quando o aplicativo desperta de estar em segundo plano e precisa restaurar o estado, ele passa o indicador para
NSUrl
. NSUrl
chama a extensão do provedor de arquivos com a URL do arquivo.- O provedor de extensão de arquivo acessa o arquivo e retorna o local do arquivo para
NSUrl
. - O local do arquivo é empacotado com informações de segurança e retornado ao aplicativo.
A partir daqui, o aplicativo pode acessar o arquivo e trabalhar com ele normalmente.
Gravando arquivos
Esta seção examinará rapidamente como funciona gravar arquivos em um local alternativo com uma Extensão de Provedor de Documentos. O aplicativo iOS usará a Coordenação de Arquivos para salvar informações em disco dentro do Contêiner de Aplicativos. Logo após o arquivo ter sido gravado com êxito, a extensão do provedor de arquivos será notificada da alteração.
Neste ponto, a extensão do provedor de arquivos pode começar a carregar o arquivo para o local alternativo (ou marcar o arquivo como sujo e exigindo upload).
Criando novas extensões de provedor de documentos
A criação de novas extensões de provedor de documentos está fora do escopo deste artigo introdutório. Essas informações são fornecidas aqui para mostrar que, com base nas extensões que um usuário carregou em seu dispositivo iOS, um aplicativo pode ter acesso a locais de armazenamento de documentos fora do local do iCloud fornecido pela Apple.
O desenvolvedor deve estar ciente desse fato ao usar o Seletor de Documentos e trabalhar com Documentos externos. Eles não devem assumir que esses documentos estão hospedados no iCloud.
Para obter mais informações sobre como criar um provedor de armazenamento ou uma extensão do seletor de documentos, consulte o documento Introdução às extensões de aplicativo.
Migrando para o iCloud Drive
No iOS 8, os usuários podem optar por continuar usando o Sistema de Documentos do iCloud existente usado no iOS 7 (e sistemas anteriores) ou podem optar por migrar os Documentos existentes para o novo mecanismo do iCloud Drive.
No Mac OS X Yosemite, a Apple não fornece a compatibilidade com versões anteriores, portanto, todos os documentos devem ser migrados para o iCloud Drive ou não serão mais atualizados entre dispositivos.
Depois que a conta de um usuário for migrada para o iCloud Drive, somente os dispositivos que usam o iCloud Drive poderão propagar alterações no Documentos nesses dispositivos.
Importante
Os desenvolvedores devem estar cientes de que os novos recursos abordados neste artigo só estarão disponíveis se a conta do usuário tiver sido migrada para o iCloud Drive.
Resumo
Este artigo abordou as alterações nas APIs existentes do iCloud necessárias para oferecer suporte ao iCloud Drive e ao novo Controlador de Exibição do Seletor de Documentos. Ele abordou a Coordenação de Arquivos e por que ela é importante ao trabalhar com documentos baseados em nuvem. Ele cobriu a configuração necessária para habilitar documentos baseados em nuvem em um aplicativo Xamarin.iOS e deu uma visão introdutória sobre como trabalhar com documentos fora do contêiner de aplicativos de um aplicativo usando o controlador de exibição do seletor de documentos.
Além disso, este artigo abordou brevemente as extensões do provedor de documentos e por que o desenvolvedor deve estar ciente delas ao escrever aplicativos que podem lidar com documentos baseados em nuvem.