Средство выбора документов в Xamarin.iOS

Средство выбора документов позволяет предоставлять доступ к документам между приложениями. Эти документы могут храниться в iCloud или в другом каталоге приложения. Документы предоставляются совместно с помощью набора расширений поставщика документов, которые пользователь установил на своем устройстве.

Из-за сложности синхронизации документов между приложениями и облаком они приводят к определенной сложности.

Требования

Для выполнения описанных в этой статье действий необходимо выполнить следующие действия.

  • Xcode 7 и iOS 8 или более поздней версии — на компьютере разработчика необходимо установить и настроить интерфейсы API Apple Xcode 7 и iOS 8 или более новых версий.
  • Visual Studio или Visual Studio для Mac — должна быть установлена последняя версия Visual Studio для Mac.
  • Устройство iOS — устройство iOS под управлением iOS 8 или более поздней версии.

Изменения в iCloud

Чтобы реализовать новые возможности средства выбора документов, в службу Apple iCloud были внесены следующие изменения:

  • Управляющая программа iCloud полностью перезаписана с помощью CloudKit.
  • Существующие функции iCloud были переименованы в iCloud Drive.
  • В iCloud добавлена поддержка ос Microsoft Windows.
  • Папка iCloud добавлена в средство поиска Mac OS.
  • Устройства iOS могут получить доступ к содержимому папки Mac OS iCloud.

Внимание

Компания Apple предоставляет инструменты, которые помогают разработчикам надлежащим образом соблюдать Общий регламент по защите данных Европейского союза (GDPR).

Что такое документ?

При обращении к документу в iCloud это отдельная автономная сущность и должна рассматриваться пользователем как такая. Пользователь может изменить документ или поделиться им с другими пользователями (например, с помощью электронной почты).

Существует несколько типов файлов, которые пользователь сразу же распознает как документы, такие как Pages, Keynote или Numbers files. Однако iCloud не ограничивается этой концепцией. Например, состояние игры (например, матч в шахматы) можно рассматривать как документ и храниться в iCloud. Этот файл можно передать между устройствами пользователя и позволить им забрать игру, в которой они покинули другое устройство.

Работа с документами

Перед погружением в код, необходимый для использования средства выбора документов с Xamarin, в этой статье рассматриваются рекомендации по работе с iCloud Documents, а также некоторые изменения, внесенные в существующие API, необходимые для поддержки средства выбора документов.

Использование координации файлов

Так как файл можно изменить из нескольких разных расположений, для предотвращения потери данных необходимо использовать координацию.

Using File Coordination

Рассмотрим приведенный выше рисунок:

  1. Устройство iOS с помощью координации файлов создает новый документ и сохраняет его в папке iCloud.
  2. iCloud сохраняет измененный файл в облаке для распространения на каждом устройстве.
  3. Подключенный Mac видит измененный файл в папке iCloud и использует координацию файлов для копирования изменений в файл.
  4. Устройство, не используюющее координацию файлов, вносит изменения в файл и сохраняет его в папке iCloud. Эти изменения мгновенно реплика на другие устройства.

Предположим, что исходное устройство iOS или Mac редактировало файл, теперь их изменения теряются и перезаписываются с версией файла с несогласованного устройства. Чтобы предотвратить потерю данных, координация файлов является обязательной при работе с облачными документами.

Использование UIDocument

UIDocument делает вещи простыми (или NSDocument в macOS), делая все тяжелые подъемы для разработчика. Она обеспечивает встроенную координацию файлов с фоновыми очередями, чтобы не блокировать пользовательский интерфейс приложения.

UIDocument предоставляет несколько высокоуровневых API, которые упрощают разработку приложения Xamarin для любой цели, которую требует разработчик.

Следующий код создает подкласс UIDocument для реализации универсального текстового документа, который можно использовать для хранения и извлечения текста из 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
    }
}

Приведенный GenericTextDocument выше класс будет использоваться в этой статье при работе с средством выбора документов и внешними документами в приложении Xamarin.iOS 8.

Асинхронная координация файлов

iOS 8 предоставляет несколько новых функций асинхронной координации файлов с помощью новых API-интерфейсов координации файлов. До iOS 8 все существующие API-интерфейсы координации файлов были полностью синхронными. Это означало, что разработчик несет ответственность за реализацию собственной фоновой очереди, чтобы предотвратить блокировку пользовательского интерфейса приложения.

Новый NSFileAccessIntent класс содержит URL-адрес, указывающий на файл, и несколько вариантов для управления типом координации, необходимым. Следующий код демонстрирует перемещение файла из одного расположения в другое с помощью намерений:

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

Обнаружение и перечисление документов

Способ обнаружения и перечисления документов — использовать существующие NSMetadataQuery API. В этом разделе рассматриваются новые функции, добавленные для NSMetadataQuery работы с документами, еще проще, чем раньше.

Существующее поведение

До iOS 8 было медленно собирать локальные изменения файла, NSMetadataQuery такие как удаление, создание и переименование.

NSMetadataQuery local file changes overview

Расшифровка схемы выше.

  1. Для файлов, которые уже существуют в контейнере приложений, имеются NSMetadata предварительно созданные и сплоченные записи, NSMetadataQuery чтобы они мгновенно были доступны приложению.
  2. Приложение создает новый файл в контейнере приложений.
  3. Перед просмотром изменения контейнера приложения возникает задержка NSMetadataQuery и создается требуемая NSMetadata запись.

Из-за задержки при создании записи приложению пришлось открыть два источника данных: один для изменений локального NSMetadata файла и один для облачных изменений.

Сшивания

В iOS 8 NSMetadataQuery проще использовать непосредственно с новой функцией под названием Stitching:

NSMetadataQuery with a new feature called Stitching

Использование стежки на приведенной выше схеме:

  1. Как и раньше, для файлов, которые уже существуют в контейнере приложений, NSMetadataQuery имеются NSMetadata предварительно созданные и сплоченные записи.
  2. Приложение создает новый файл в контейнере приложений с помощью координации файлов.
  3. Перехватчик в контейнере приложений видит изменения и вызовы NSMetadataQuery для создания требуемой NSMetadata записи.
  4. Запись NSMetadata создается непосредственно после файла и становится доступной приложению.

При использовании стежки приложения больше не нужно открывать источник данных для отслеживания изменений локальных и облачных файлов. Теперь приложение может напрямую полагаться NSMetadataQuery .

Внимание

Стежка работает только в том случае, если приложение использует координацию файлов, как показано в приведенном выше разделе. Если координация файлов не используется, API по умолчанию применяются к существующему поведению до iOS 8.

Новые функции метаданных iOS 8

В iOS 8 добавлены NSMetadataQuery следующие новые функции:

  • NSMetatadataQuery Теперь список не локальных документов, хранящихся в облаке.
  • Добавлены новые API для доступа к метаданным в облачных документах.
  • Существует новый NSUrl_PromisedItems API, который будет получать доступ к атрибутам файлов, которые могут или не могут иметь их содержимое локально.
  • GetPromisedItemResourceValue Используйте метод, чтобы получить сведения о заданном файле или использовать GetPromisedItemResourceValues метод для получения сведений о нескольких файлах одновременно.

Добавлены два новых флага координации файлов для работы с метаданными:

  • NSFileCoordinatorReadImmediatelyAvailableMetadataOnly
  • NSFileCoordinatorWriteContentIndependentMetadataOnly

С указанными выше флагами содержимое файла документа не должно быть доступно локально для их использования.

В следующем сегменте кода показано, как использовать NSMetadataQuery для запроса к существованию определенного файла и сборки файла, если он не существует:

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

Эскизы документов

Apple чувствует, что лучший пользовательский интерфейс при перечислении документов для приложения — использовать предварительные версии. Это дает контекст конечным пользователям, чтобы быстро определить документ, с которыми они хотят работать.

До iOS 8, показывающее предварительные версии документов, необходимые для пользовательской реализации. Новые атрибуты файловой системы iOS 8 позволяют разработчику быстро работать с эскизами документов.

Получение эскизов документов

Вызывая GetPromisedItemResourceValue методы или GetPromisedItemResourceValues методы NSUrl_PromisedItems , API, NSUrlThumbnailDictionaryвозвращается. Единственным ключом в данный момент в этом словаре является NSThumbnial1024X1024SizeKey соответствующий и соответствующий UIImage.

Сохранение эскизов документов

Самый простой способ сохранения эскиза — использовать UIDocument. GetFileAttributesToWrite Вызвав метод UIDocument и задав эскиз, он будет автоматически сохранен при создании файла документа. Управляющая программа iCloud увидит это изменение и распространяет его в iCloud. В Mac OS X эскизы автоматически создаются для разработчика подключаемым модулем быстрого просмотра.

Используя основы работы с документами на основе iCloud, а также изменения существующего API, мы готовы реализовать контроллер представления документов в мобильном приложении Xamarin iOS 8.

Включение iCloud в Xamarin

Прежде чем средство выбора документов можно будет использовать в приложении Xamarin.iOS, поддержка iCloud должна быть включена как в приложении, так и через Apple.

В следующих шагах описан процесс подготовки для iCloud.

  1. Создание контейнера iCloud.
  2. Создайте идентификатор приложения, содержащий Служба приложений iCloud.
  3. Создайте профиль подготовки, содержащий этот идентификатор приложения.

В руководстве по работе с возможностями рассматриваются первые два шага. Чтобы создать профиль подготовки, выполните действия, описанные в руководстве по профилю подготовки.

Ниже приведены инструкции по настройке приложения для iCloud:

Выполните следующие действия.

  1. Откройте проект в Visual Studio для Mac или Visual Studio.

  2. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите пункт "Параметры".

  3. В диалоговом окне "Параметры" выберите приложение iOS, убедитесь, что идентификатор пакета соответствует идентификатору пакета, определенному в идентификаторе приложения, созданном выше.

  4. Выберите подпись пакета iOS, выберите удостоверение разработчика и созданный выше профиль подготовки.

  5. Нажмите кнопку "ОК", чтобы сохранить изменения и закрыть диалоговое окно.

  6. Щелкните правой кнопкой мыши Entitlements.plistОбозреватель решений, чтобы открыть его в редакторе.

    Внимание

    В Visual Studio вам может потребоваться открыть редактор прав, щелкнув его правой кнопкой мыши, выбрав "Открыть с помощью" и выбрав редактор списка свойств.

  7. Проверьте включение iCloud, документов iCloud, хранилища ключей и CloudKit.

  8. Убедитесь, что контейнер существует для приложения (как было создано выше). Пример: iCloud.com.your-company.AppName

  9. Сохраните изменения в файле.

Дополнительные сведения о правах см. в руководстве по работе с правами.

После установки выше приложение теперь может использовать облачные документы и новый контроллер представления средства выбора документов.

Общий код установки

Перед началом работы с контроллером представления средства выбора документов требуется стандартный код установки. Начните с изменения файла приложения AppDelegate.cs и сделайте его следующим образом:

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

Внимание

Приведенный выше код содержит код из раздела "Обнаружение и перечисление документов". Она представлена здесь в целом, так как она будет отображаться в фактическом приложении. Для простоты этот пример работает только с одним жестко закодированным файлом (test.txt).

Приведенный выше код предоставляет несколько сочетаний клавиш iCloud Drive, чтобы упростить работу с остальными приложениями.

Затем добавьте следующий код в любой контейнер представления или представления, который будет использовать средство выбора документов или работать с облачными документами:

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

Это добавляет ярлык для доступа к AppDelegate ярлыкам iCloud, созданным выше.

В этом коде мы рассмотрим реализацию контроллера представления выбора документов в приложении Xamarin iOS 8.

Использование контроллера представления средства выбора документов

До iOS 8 было очень трудно получить доступ к документам из другого приложения, так как нет способа обнаружить документы за пределами приложения из приложения.

Существующее поведение

Existing Behavior overview

Давайте рассмотрим доступ к внешнему документу до iOS 8:

  1. Сначала пользователю потребуется открыть приложение, которое изначально создало документ.
  2. Выбран документ и UIDocumentInteractionController используется для отправки документа в новое приложение.
  3. Наконец, копия исходного документа помещается в контейнер нового приложения.

Оттуда документ доступен для второго приложения, чтобы открыть и изменить его.

Обнаружение документов за пределами контейнера приложения

В iOS 8 приложение может получить доступ к документам за пределами собственного контейнера приложений с легкостью:

Discovering Documents Outside of an App's Container

С помощью нового средства выбора документов iCloud ( UIDocumentPickerViewController) приложение iOS может напрямую обнаруживать и получать доступ за пределами своего контейнера приложений. Этот UIDocumentPickerViewController механизм позволяет пользователю предоставлять доступ к обнаруженным документам и изменять их с помощью разрешений.

Приложение должно иметь возможность отображать свои документы в средстве выбора документов iCloud и быть доступным для других приложений для обнаружения и работы с ними. Чтобы приложение Xamarin iOS 8 поделилось своим контейнером приложений, измените его Info.plist файл в стандартном текстовом редакторе и добавьте следующие две строки в нижней части словаря (между <dict>...</dict> тегами):

<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>

Предоставляет UIDocumentPickerViewController отличный новый пользовательский интерфейс, позволяющий пользователю выбирать документы. Чтобы отобразить контроллер представления средства выбора документов в приложении Xamarin iOS 8, сделайте следующее:

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

Внимание

Разработчик должен вызвать StartAccessingSecurityScopedResource метод NSUrl перед доступом к внешнему документу. Метод StopAccessingSecurityScopedResource должен вызываться, чтобы освободить блокировку безопасности после загрузки документа.

Образец вывода

Ниже приведен пример отображения приведенного выше кода средства выбора документов при запуске на устройстве i Телефон:

  1. Пользователь запускает приложение и отображается основной интерфейс:

    The main interface is displayed

  2. Пользователь нажимает кнопку "Действие" в верхней части экрана и запрашивает выбрать поставщика документов из списка доступных поставщиков:

    Select a Document Provider from the list of available providers

  3. Контроллер представления средства выбора документов отображается для выбранного поставщика документов:

    The Document Picker View Controller is displayed

  4. Пользователь нажимает на папку документа, чтобы отобразить его содержимое:

    The Document Folder contents

  5. Пользователь выбирает документ и средство выбора документов закрывается.

  6. Основной интерфейс переигрывается, документ загружается из внешнего контейнера и отображается его содержимое.

Фактическое отображение контроллера представления средства выбора документов зависит от поставщиков документов, установленных пользователем на устройстве, и от того, какой режим выбора документов был реализован. Приведенный выше пример использует открытый режим, другие типы режимов будут подробно рассмотрены ниже.

Управление внешними документами

Как описано выше, до iOS 8 приложение может получить доступ только к документам, которые были частью своего контейнера приложений. В iOS 8 приложение может получить доступ к документам из внешних источников:

Managing External Documents overview

Когда пользователь выбирает документ из внешнего источника, справочный документ записывается в контейнер приложения, указывающий на исходный документ.

Чтобы помочь в добавлении этой новой возможности в существующие приложения, в API были добавлены NSMetadataQuery несколько новых функций. Как правило, приложение использует область ubiquitous document для перечисления документов, активных в контейнере приложения. С помощью этого область будут отображаться только документы в контейнере приложений.

С помощью новой области внешних документов будут возвращены документы, которые находятся за пределами контейнера приложения, и возвращают метаданные для них. Будет NSMetadataItemUrlKey указан URL-адрес, в котором находится документ.

Иногда приложение не хочет работать с документами, на которые указывает ссылка. Вместо этого приложение хочет напрямую работать с справочным документом. Например, приложению может потребоваться отобразить документ в папке приложения в пользовательском интерфейсе или разрешить пользователю перемещать ссылки в папку.

В iOS 8 был предоставлен новый NSMetadataItemUrlInLocalContainerKey доступ к справочному документу напрямую. Этот ключевой элемент указывает на фактическую ссылку на внешний документ в контейнере приложений.

Используется NSMetadataUbiquitousItemIsExternalDocumentKey для проверки того, является ли документ внешним для контейнера приложения. Используется NSMetadataUbiquitousItemContainerDisplayNameKey для доступа к имени контейнера, в который находится исходная копия внешнего документа.

Почему требуются ссылки на документы

Основная причина, по которой iOS 8 использует ссылки на доступ к внешним документам, — безопасность. Ни одно приложение не предоставляет доступ к контейнеру другого приложения. Это может сделать только средство выбора документов, так как выполняется вне процесса и имеет широкий системный доступ.

Единственным способом добраться до документа за пределами контейнера приложений является использование средства выбора документов, и если URL-адрес, возвращаемый средством выбора, является областью безопасности. URL-адрес области безопасности содержит достаточно сведений, чтобы выбрать документ вместе с правами область, необходимыми для предоставления приложению доступа к документу.

Важно отметить, что если URL-адрес области безопасности сериализован в строку, а затем десериализирован, сведения о безопасности будут потеряны, а файл будет недоступным из URL-адреса. Функция справочника по документам предоставляет механизм возврата к файлам, на которые указываются эти URL-адреса.

Таким образом, если приложение получает из одного из справочных документов, оно уже подключено NSUrl область безопасности и может использоваться для доступа к файлу. По этой причине настоятельно рекомендуется использовать разработчика UIDocument , так как он обрабатывает всю эту информацию и процессы для них.

Использование закладок

Невозможно перечислить документы приложения, чтобы вернуться к конкретному документу, например при восстановлении состояния. iOS 8 предоставляет механизм создания закладок, предназначенных непосредственно для данного документа.

Следующий код создаст закладку из UIDocumentFileUrl свойства:

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

Существующий API закладки используется для создания закладки для существующего NSUrl , который можно сохранить и загрузить, чтобы предоставить прямой доступ к внешнему файлу. Следующий код восстановит закладку, созданную выше:

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

Открытие и режим импорта и средство выбора документов

Контроллер представления средства выбора документов включает два разных режима работы:

  1. Открыть режим . В этом режиме, когда пользователь выбирает и внешний документ, средство выбора документов создаст закладку с областью безопасности в контейнере приложения.

    A Security Scoped Bookmark in the Application Container

  2. Режим импорта. В этом режиме, когда пользователь выбирает и внешний документ, средство выбора документов не создаст закладку, а скопируйте файл в временное расположение и предоставьте приложению доступ к документу в этом расположении:

    The Document Picker will copy the file into a Temporary Location and provide the application access to the Document at this location
    Когда приложение завершится по какой-либо причине, временное расположение очищается и удаляется файл. Если приложению нужен доступ к файлу, он должен сделать копию и поместить ее в контейнер приложения.

Открытый режим полезен, когда приложение хочет сотрудничать с другим приложением и совместно использовать любые изменения, внесенные в документ с этим приложением. Режим импорта используется, если приложение не хочет совместно использовать изменения документа с другими приложениями.

Создание внешнего документа

Как отмечалось выше, приложение iOS 8 не имеет доступа к контейнерам за пределами собственного контейнера приложений. Приложение может записывать данные в собственный контейнер локально или в временное расположение, а затем использовать специальный режим документа для перемещения итогового документа за пределы контейнера приложения в выбранное пользователем расположение.

Чтобы переместить документ во внешнее расположение, сделайте следующее:

  1. Сначала создайте документ в локальном или временном расположении.
  2. NSUrl Создайте новый документ.
  3. Откройте новый контроллер представления средства выбора документов и передайте его NSUrl в режиме MoveToService .
  4. После выбора нового расположения документ будет перемещен из текущего расположения в новое расположение.
  5. Справочный документ будет записан в контейнер приложений приложения, чтобы файл по-прежнему был доступ к созданному приложению.

Следующий код можно использовать для перемещения документа в внешнее расположение: var picker = new UIDocumentPickerViewController (srcURL, UIDocumentPickerMode.MoveToService);

Справочный документ, возвращаемый приведенным выше процессом, точно совпадает с тем, что создан в режиме открытия средства выбора документов. Однако в приложении может потребоваться переместить документ, не сохраняя ссылку на нее.

Чтобы переместить документ без создания ссылки, используйте ExportToService режим. Пример: var picker = new UIDocumentPickerViewController (srcURL, UIDocumentPickerMode.ExportToService);

При использовании ExportToService режима документ копируется во внешний контейнер, а существующая копия остается в исходном расположении.

Расширения поставщика документов

С iOS 8 Apple хочет, чтобы пользователь мог получить доступ к любому из облачных документов независимо от того, где они существуют. Для достижения этой цели iOS 8 предоставляет новый механизм расширения поставщика документов.

Что такое расширение поставщика документов?

Просто говоря, расширение поставщика документов — это способ для разработчика или стороннего разработчика, чтобы предоставить приложению альтернативное хранилище документов, которое можно получить точно так же, как существующее расположение хранилища iCloud.

Пользователь может выбрать одно из этих альтернативных расположений хранилища в средстве выбора документов, и они могут использовать одинаковые режимы доступа (Open, Import, Move или Export) для работы с файлами в этом расположении.

Это реализуется с помощью двух разных расширений:

  • Расширение средства выбора документов — предоставляет UIViewController подкласс, который предоставляет графический интерфейс для пользователя, чтобы выбрать документ из альтернативного расположения хранилища. Этот подкласс будет отображаться в составе контроллера представления средства выбора документов.
  • Расширение предоставления файлов — это расширение , не относящееся к пользовательскому интерфейсу, которое занимается фактически предоставлением содержимого файлов. Эти расширения предоставляются через координацию файлов ( NSFileCoordinator ). Это еще один важный случай, когда требуется координация файлов.

На следующей схеме показан типичный поток данных при работе с расширениями поставщика документов:

This diagram shows the typical data flow when working with Document Provider Extensions

Происходит следующий процесс:

  1. Приложение представляет контроллер средства выбора документов, чтобы разрешить пользователю выбрать файл для работы.
  2. Пользователь выбирает альтернативное расположение файла и вызывается настраиваемое UIViewController расширение для отображения пользовательского интерфейса.
  3. Пользователь выбирает файл из этого расположения и URL-адрес передается обратно в средство выбора документов.
  4. Средство выбора документов выбирает URL-адрес файла и возвращает его приложению, чтобы пользователь работал над ним.
  5. URL-адрес передается координатору файлов, чтобы вернуть содержимое файлов приложению.
  6. Координатор файлов вызывает пользовательское расширение поставщика файлов для получения файла.
  7. Содержимое файла возвращается координатору файлов.
  8. Содержимое файла возвращается приложению.

Безопасность и закладки

В этом разделе описано, как безопасность и постоянный доступ к файлам с помощью закладок работает с расширениями поставщика документов. В отличие от поставщика документов iCloud, который автоматически сохраняет безопасность и закладки в контейнер приложения, расширения поставщика документов не являются частью системы ссылок на документы.

Например, в параметре Enterprise, который предоставляет собственное защищенное хранилище данных компании, администраторы не хотят получать доступ к конфиденциальной корпоративной информации или обрабатываться общедоступными серверами iCloud. Поэтому встроенная система ссылок на документы не может использоваться.

Система закладок по-прежнему может использоваться, и она отвечает за расширение поставщика файлов для правильной обработки закладочного URL-адреса и возврата содержимого документа, на который он указывает.

В целях безопасности iOS 8 имеет уровень изоляции, который сохраняет сведения о том, какое приложение имеет доступ к идентификатору, в котором находится поставщик файлов. Следует отметить, что весь доступ к файлам контролируется этим уровнем изоляции.

На следующей схеме показан поток данных при работе с закладками и расширением поставщика документов:

This diagram shows the data flow when working with Bookmarks and a Document Provider Extension

Происходит следующий процесс:

  1. Приложение будет входить в фон и должно сохранять свое состояние. Он вызывает NSUrl создание закладки в файл в альтернативном хранилище.
  2. NSUrl вызывает расширение поставщика файлов, чтобы получить постоянный URL-адрес документа.
  3. Расширение поставщика файлов возвращает URL-адрес в виде строки NSUrl .
  4. Url-адрес NSUrl упаковает в закладку и возвращает его приложению.
  5. Когда приложение проснется от того, как оно в фоновом режиме и должно восстановить состояние, оно передает закладку NSUrl в .
  6. NSUrl вызывает расширение поставщика файлов с URL-адресом файла.
  7. Поставщик расширений файлов обращается к файлу и возвращает расположение файла NSUrl .
  8. Расположение файла упаковается с информацией о безопасности и возвращается в приложение.

Отсюда приложение может получить доступ к файлу и работать с ним как обычно.

Написание файлов

В этом разделе показано, как работает запись файлов в альтернативное расположение с расширением поставщика документов. Приложение iOS будет использовать координацию файлов для сохранения сведений на диск в контейнере приложений. Вскоре после успешной записи файла расширение поставщика файлов будет уведомлено об изменении.

На этом этапе расширение поставщика файлов может начать отправку файла в альтернативное расположение (или пометить файл как грязное и требовать отправки).

Создание расширений поставщика документов

Создание расширений поставщика документов выходит за рамки область этой вводной статьи. Эта информация представлена здесь, чтобы показать, что на основе расширений, загруженных пользователем на устройстве iOS, приложение может иметь доступ к расположениям хранилища документов за пределами предоставленного iCloud расположения Apple.

Разработчик должен учитывать этот факт при использовании средства выбора документов и работе с внешними документами. Они не должны предполагать, что эти документы размещаются в iCloud.

Дополнительные сведения о создании служба хранилища поставщика или расширения средства выбора документов см. в документе "Введение в расширения приложений".

Перенос на iCloud Drive

В iOS 8 пользователи могут продолжить использование существующей системы документов iCloud, используемой в iOS 7 (и предыдущих системах) или перенести существующие документы в новый механизм iCloud Drive.

В Mac OS X Yosemite Apple не обеспечивает обратную совместимость, поэтому все документы должны быть перенесены на iCloud Drive или они больше не будут обновляться на разных устройствах.

После переноса учетной записи пользователя на iCloud Drive только устройства, использующие iCloud Drive, смогут распространять изменения на документы на этих устройствах.

Внимание

Разработчики должны знать, что новые функции, описанные в этой статье, доступны только в том случае, если учетная запись пользователя перенесена на iCloud Drive.

Итоги

В этой статье рассматриваются изменения существующих API iCloud, необходимые для поддержки iCloud Drive и нового контроллера представления средства выбора документов. Она рассмотрела координацию файлов и почему важно при работе с облачными документами. В ней описана настройка, необходимая для включения облачных документов в приложении Xamarin.iOS и вводные сведения о работе с документами за пределами контейнера приложения с помощью контроллера представления средства выбора документов.

Кроме того, в этой статье кратко рассматриваются расширения поставщика документов и почему разработчик должен учитывать их при написании приложений, которые могут обрабатывать облачные документы.