Расширения iOS в Xamarin.iOS

Создание расширений в видео iOS

Расширения, представленные в iOS 8, специализированы UIViewControllers , которые представлены iOS в стандартных контекстах, таких как в Центре уведомлений, как пользовательские типы клавиатуры, запрашиваемые пользователем для выполнения специализированных входных данных или других контекстов, таких как редактирование фотографии, где расширение может предоставлять специальные фильтры эффектов.

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

Точки расширения

Тип Описание Точка расширения Хост-приложение
Действие Специализированный редактор или средство просмотра для определенного типа мультимедиа com.apple.ui-services Любое
Поставщик документов Позволяет приложению использовать удаленное хранилище документов com.apple.fileprovider-ui Приложения с помощью UIDocumentPickerViewController
Клавиатура Альтернативные клавиатуры com.apple.keyboard-service Любое
Редактирование фотографий Обработка фотографий и редактирование com.apple.photo-editing редактор Photos.app
Общий доступ Обмен данными с социальными сетями, службами обмена сообщениями и т. д. com.apple.share-services Любое
Today "Мини-приложения", которые отображаются на экране "Сегодня" или в Центре уведомлений com.apple.widget-extensions Сегодня и Центр уведомлений

В iOS 10 и iOS 12 добавлены дополнительные точки расширения. Полную таблицу всех поддерживаемых типов можно найти в руководстве по программированию расширений приложений iOS.

Ограничения

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

Универсальные ограничения:

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

Распространение, установка и запуск расширений

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

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

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

Отладка и выпуск версий расширений

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

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

  1. Создайте пакет приложения в конфигурации выпуска .
  2. В параметрах проекта сборки iOS задайте для параметра поведения компоновщика значение Link Framework SDK Only или Link All.
  3. В параметрах проекта отладки iOS отмените проверка параметр "Включить отладку" и "Включить профилирование".

Жизненный цикл расширения

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

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

Расширения могут взаимодействовать с приложениями узла через объект NSExtensionContext . Некоторые расширения имеют операции, получающие асинхронные обратные вызовы с результатами. Эти обратные вызовы будут выполняться в фоновых потоках, и расширение должно учитывать это; например, с помощью NSObject.InvokeOnMainThread , если они хотят обновить пользовательский интерфейс. Дополнительные сведения см. в разделе "Взаимодействие с приложением узла" ниже.

По умолчанию расширения и их контейнерные приложения не могут взаимодействовать, несмотря на то, что они установлены вместе. В некоторых случаях приложение-контейнер контейнера по сути является пустым контейнером "доставка", назначение которого выполняется после установки расширения. Однако если обстоятельства диктуют, приложение-контейнер и расширение могут совместно использовать ресурсы из общей области. Кроме того, расширение Today может запросить свое приложение-контейнер, чтобы открыть URL-адрес. Это поведение отображается в мини-приложении счетчика событий.

Создание расширения

Расширения (и их приложения-контейнеры) должны быть 64-разрядными двоичными файлами и создаваться с помощью унифицированных API Xamarin.iOS. При разработке расширения решения будут содержать по крайней мере два проекта: приложение-контейнер и один проект для каждого расширения, который предоставляет контейнер.

Требования к проекту приложения-контейнера

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

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

Требования к проекту расширения

Кроме того, проект расширения имеет следующие требования:

  • Он должен иметь идентификатор пакета, который начинается с идентификатора пакета приложения-контейнера. Например, если у приложения-контейнера есть идентификатор com.myCompany.ContainerAppпакета, идентификатор расширения может быть com.myCompany.ContainerApp.MyExtension:

    Bundle identifiers

  • Он должен определить ключ NSExtensionPointIdentifierс соответствующим значением (например com.apple.widget-extension , для мини-приложения Центра уведомлений сегодня ) в файле Info.plist .

  • Он также должен определить NSExtensionMainStoryboard ключ или NSExtensionPrincipalClass ключ в файле Info.plist с соответствующим значением:

    • NSExtensionMainStoryboard Используйте ключ, чтобы указать имя раскадровки, представляющее основной пользовательский интерфейс расширения (минус.storyboard). Например, Main для Main.storyboard файла.
    • NSExtensionPrincipalClass Используйте ключ, чтобы указать класс, который будет инициализирован при запуске расширения. Значение должно соответствовать значению register объекта UIViewController:

    Principal class registration

Определенные типы расширений могут иметь дополнительные требования. Например, основной класс расширения "Сегодня " или "Центр уведомлений" должен реализовать INCWidgetProviding.

Внимание

Если вы запускаете проект с помощью одного из шаблонов расширений, предоставляемых Visual Studio для Mac, большинство (если не все) эти требования будут предоставлены и выполнены автоматически шаблоном.

Пошаговое руководство

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

An example Today widget that calculates the day and number of days remaining in the year

Создание решения

Чтобы создать необходимое решение, сделайте следующее:

  1. Сначала создайте проект приложения с одним представлением iOS и нажмите кнопку "Далее":

    First, create a new iOS, Single View App project and click the Next button

  2. Вызовите проект TodayContainer и нажмите кнопку "Далее ":

    Call the project TodayContainer and click the Next button

  3. Проверьте имя проекта и имя решения и нажмите кнопку "Создать", чтобы создать решение:

    Verify the Project Name and SolutionName and click the Create button to create the solution

  4. Затем в Обозреватель решений щелкните правой кнопкой мыши решение и добавьте новый проект расширения iOS из шаблона расширения today:

    Next, in the Solution Explorer, right-click on the Solution and add a new iOS Extension project from the Today Extension template

  5. Вызовите проект DaysRemaining и нажмите кнопку "Далее ":

    Call the project DaysRemaining and click the Next button

  6. Просмотрите проект и нажмите кнопку "Создать ", чтобы создать ее:

    Review the project and click the Create button to create it

Теперь результирующее решение должно иметь два проекта, как показано здесь:

The resulting Solution should now have two projects, as shown here

Создание пользовательского интерфейса расширения

Затем вам потребуется разработать интерфейс для мини-приложения Today . Это можно сделать с помощью раскадровки или создания пользовательского интерфейса в коде. Оба метода подробно описаны ниже.

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

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

  1. В Обозреватель решений дважды щелкните файл проекта Main.storyboard расширения, чтобы открыть его для редактирования:

    Double-click the Extension projects Main.storyboard file to open it for editing

  2. Выберите метку, которая была автоматически добавлена в пользовательский интерфейс по шаблону и присвойте ему имяTodayMessage на вкладке "Мини-приложение" Обозреватель свойств:

    Select the Label that was automatically added to the UI by template and give it the Name TodayMessage in the Widget tab of the Properties Explorer

  3. Сохраните изменения в раскадровке.

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

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

  1. В Обозреватель решений выберите проект DaysRemaining, добавьте новый класс и вызовите егоCodeBasedViewController:

    Aelect the DaysRemaining project, add a new class and call it CodeBasedViewController

  2. Снова в Обозреватель решений дважды щелкните файл расширенияInfo.plist, чтобы открыть его для редактирования:

    Double-click Extensions Info.plist file to open it for editing

  3. Выберите исходное представление (в нижней части экрана) и откройте NSExtension узел:

    Select the Source View from the bottom of the screen and open the NSExtension node

  4. NSExtensionMainStoryboard Удалите ключ и добавьте NSExtensionPrincipalClass значениеCodeBasedViewController:

    Remove the NSExtensionMainStoryboard key and add a NSExtensionPrincipalClass with the value CodeBasedViewController

  5. Сохранение изменений.

Затем измените CodeBasedViewController.cs файл и сделайте его следующим образом:

using System;
using Foundation;
using UIKit;
using NotificationCenter;
using CoreGraphics;

namespace DaysRemaining
{
  [Register("CodeBasedViewController")]
  public class CodeBasedViewController : UIViewController, INCWidgetProviding
  {
    public CodeBasedViewController ()
    {
    }

    public override void ViewDidLoad ()
    {
      base.ViewDidLoad ();

      // Add label to view
      var TodayMessage = new UILabel (new CGRect (0, 0, View.Frame.Width, View.Frame.Height)) {
        TextAlignment = UITextAlignment.Center
      };

      View.AddSubview (TodayMessage);

      // Insert code to power extension here...

    }
  }
}

Обратите внимание, что значение [Register("CodeBasedViewController")] , указанное для приведенного NSExtensionPrincipalClass выше.

Написание кода расширения

Создав пользовательский интерфейс, откройте TodayViewController.cs файл или CodeBasedViewController.cs файл (на основе метода, используемого для создания пользовательского интерфейса выше), измените метод ViewDidLoad и сделайте его следующим образом:

public override void ViewDidLoad ()
{
  base.ViewDidLoad ();

  // Calculate the values
  var dayOfYear = DateTime.Now.DayOfYear;
  var leapYearExtra = DateTime.IsLeapYear (DateTime.Now.Year) ? 1 : 0;
  var daysRemaining = 365 + leapYearExtra - dayOfYear;

  // Display the message
  if (daysRemaining == 1) {
    TodayMessage.Text = String.Format ("Today is day {0}. There is one day remaining in the year.", dayOfYear);
  } else {
    TodayMessage.Text = String.Format ("Today is day {0}. There are {1} days remaining in the year.", dayOfYear, daysRemaining);
  }
}

При использовании метода пользовательского интерфейса на основе кода замените // Insert code to power extension here... комментарий новым кодом, приведенным выше. После вызова базовой реализации (и вставки метки для версии на основе кода) этот код выполняет простое вычисление, чтобы получить день года и сколько дней осталось. Затем отображается сообщение в метке (TodayMessage), созданной в конструкторе пользовательского интерфейса.

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

Создание пользовательского интерфейса приложения-контейнера

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

Edit the TodayContainers Main.storyboard file and add some text defining the Extensions function and how to install it

Сохраните изменения в раскадровке.

Тестирование расширения

Чтобы протестировать расширение в симуляторе iOS, запустите приложение TodayContainer . Откроется основное представление контейнера:

The containers main view will be displayed

Затем нажмите кнопку "Главная " в симуляторе, проводите пальцем вниз в верхней части экрана, чтобы открыть центр уведомлений, перейдите на вкладку "Сегодня " и нажмите кнопку " Изменить ".

Hit the Home button in the Simulator, swipe down from the top of the screen to open the Notification Center, select the Today tab and click the Edit button

Добавьте расширение DaysRemaining в представление "Сегодня" и нажмите кнопку "Готово":

Add the DaysRemaining Extension to the Today view and click the Done button

Новое мини-приложение будет добавлено в представление "Сегодня" , и результаты будут отображаться:

The new widget will be added to the Today view and the results will be displayed

Взаимодействие с ведущим приложением

Пример созданного выше расширения today не взаимодействует с его ведущим приложением ( на экране "Сегодня "). Если бы он сделал, он будет использовать свойство ExtensionContext для TodayViewController классов или CodeBasedViewController классов.

Для расширений, которые будут получать данные из своих ведущих приложений, данные хранятся в виде массива объектов NSExtensionItem, хранящихся в свойстве InputItems расширенияContext расширенияUIViewController.

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

Дополнительные сведения см. в руководстве по программированию расширений приложений Apple.

Взаимодействие с родительским приложением

Группы приложений предоставляют различным приложениям (или одному приложению и его расширениям) общее место хранения файлов. Группы приложений можно использовать для следующих данных:

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

MobileCoreServices

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

Статический MobileCoreServices.UTType класс определяет следующие вспомогательные свойства, относящиеся к определениям Apple kUTType... :

  • kUTTypeAlembic - Alembic
  • kUTTypeAliasFile - AliasFile
  • kUTTypeAliasRecord - AliasRecord
  • kUTTypeAppleICNS - AppleICNS
  • kUTTypeAppleProtectedMPEG4Audio - AppleProtectedMPEG4Audio
  • kUTTypeAppleProtectedMPEG4Video - AppleProtectedMPEG4Video
  • kUTTypeAppleScript - AppleScript
  • kUTTypeApplication - Application
  • kUTTypeApplicationBundle - ApplicationBundle
  • kUTTypeApplicationFile - ApplicationFile
  • kUTTypeArchive - Archive
  • kUTTypeAssemblyLanguageSource - AssemblyLanguageSource
  • kUTTypeAudio - Audio
  • kUTTypeAudioInterchangeFileFormat - AudioInterchangeFileFormat
  • kUTTypeAudiovisualContent - AudiovisualContent
  • kUTTypeAVIMovie - AVIMovie
  • kUTTypeBinaryPropertyList - BinaryPropertyList
  • kUTTypeBMP - BMP
  • kUTTypeBookmark - Bookmark
  • kUTTypeBundle - Bundle
  • kUTTypeBzip2Archive - Bzip2Archive
  • kUTTypeCalendarEvent - CalendarEvent
  • kUTTypeCHeader - CHeader
  • kUTTypeCommaSeparatedText - CommaSeparatedText
  • kUTTypeCompositeContent - CompositeContent
  • kUTTypeConformsToKey - ConformsToKey
  • kUTTypeContact - Contact
  • kUTTypeContent - Content
  • kUTTypeCPlusPlusHeader - CPlusPlusHeader
  • kUTTypeCPlusPlusSource - CPlusPlusSource
  • kUTTypeCSource - CSource
  • kUTTypeData - Database
  • kUTTypeDelimitedText - DelimitedText
  • kUTTypeDescriptionKey - DescriptionKey
  • kUTTypeDirectory - Directory
  • kUTTypeDiskImage - DiskImage
  • kUTTypeElectronicPublication - ElectronicPublication
  • kUTTypeEmailMessage - EmailMessage
  • kUTTypeExecutable - Executable
  • kUTExportedTypeDeclarationsKey - ExportedTypeDeclarationsKey
  • kUTTypeFileURL - FileURL
  • kUTTypeFlatRTFD - FlatRTFD
  • kUTTypeFolder - Folder
  • kUTTypeFont - Font
  • kUTTypeFramework - Framework
  • kUTTypeGIF - GIF
  • kUTTypeGNUZipArchive - GNUZipArchive
  • kUTTypeHTML - HTML
  • kUTTypeICO - ICO
  • kUTTypeIconFileKey - IconFileKey
  • kUTTypeIdentifierKey - IdentifierKey
  • kUTTypeImage - Image
  • kUTImportedTypeDeclarationsKey - ImportedTypeDeclarationsKey
  • kUTTypeInkText - InkText
  • kUTTypeInternetLocation - InternetLocation
  • kUTTypeItem - Item
  • kUTTypeJavaArchive - JavaArchive
  • kUTTypeJavaClass - JavaClass
  • kUTTypeJavaScript - JavaScript
  • kUTTypeJavaSource - JavaSource
  • kUTTypeJPEG - JPEG
  • kUTTypeJPEG2000 - JPEG2000
  • kUTTypeJSON - JSON
  • kUTType3dObject - k3dObject
  • kUTTypeLivePhoto - LivePhoto
  • kUTTypeLog - Log
  • kUTTypeM3UPlaylist - M3UPlaylist
  • kUTTypeMessage - Message
  • kUTTypeMIDIAudio - MIDIAudio
  • kUTTypeMountPoint - MountPoint
  • kUTTypeMovie - Movie
  • kUTTypeMP3 - MP3
  • kUTTypeMPEG - MPEG
  • kUTTypeMPEG2TransportStream - MPEG2TransportStream
  • kUTTypeMPEG2Video - MPEG2Video
  • kUTTypeMPEG4 - MPEG4
  • kUTTypeMPEG4Audio - MPEG4Audio
  • kUTTypeObjectiveCPlusPlusSource - ObjectiveCPlusPlusSource
  • kUTTypeObjectiveCSource - ObjectiveCSource
  • kUTTypeOSAScript - OSAScript
  • kUTTypeOSAScriptBundle - OSAScriptBundle
  • kUTTypePackage - Package
  • kUTTypePDF - PDF
  • kUTTypePerlScript - PerlScript
  • kUTTypePHPScript - PHPScript
  • kUTTypePICT - PICT
  • kUTTypePKCS12 - PKCS12
  • kUTTypePlainText - PlainText
  • kUTTypePlaylist - Playlist
  • kUTTypePluginBundle - PluginBundle
  • kUTTypePNG - PNG
  • kUTTypePolygon - Polygon
  • kUTTypePresentation - Presentation
  • kUTTypePropertyList - PropertyList
  • kUTTypePythonScript - PythonScript
  • kUTTypeQuickLookGenerator - QuickLookGenerator
  • kUTTypeQuickTimeImage - QuickTimeImage
  • kUTTypeQuickTimeMovie - QuickTimeMovie
  • kUTTypeRawImage - RawImage
  • kUTTypeReferenceURLKey - ReferenceURLKey
  • kUTTypeResolvable - Resolvable
  • kUTTypeRTF - RTF
  • kUTTypeRTFD - RTFD
  • kUTTypeRubyScript - RubyScript
  • kUTTypeScalableVectorGraphics - ScalableVectorGraphics
  • kUTTypeScript - Script
  • kUTTypeShellScript - ShellScript
  • kUTTypeSourceCode - SourceCode
  • kUTTypeSpotlightImporter - SpotlightImporter
  • kUTTypeSpreadsheet - Spreadsheet
  • kUTTypeStereolithography - Stereolithography
  • kUTTypeSwiftSource - SwiftSource
  • kUTTypeSymLink - SymLink
  • kUTTypeSystemPreferencesPane - SystemPreferencesPane
  • kUTTypeTabSeparatedText - TabSeparatedText
  • kUTTagClassFilenameExtension - TagClassFilenameExtension
  • kUTTagClassMIMEType - TagClassMIMEType
  • kUTTypeTagSpecificationKey - TagSpecificationKey
  • kUTTypeText - Text
  • kUTType3DContent - ThreeDContent
  • kUTTypeTIFF - TIFF
  • kUTTypeToDoItem - ToDoItem
  • kUTTypeTXNTextAndMultimediaData - TXNTextAndMultimediaData
  • kUTTypeUniversalSceneDescription - UniversalSceneDescription
  • kUTTypeUnixExecutable - UnixExecutable
  • kUTTypeURL - URL
  • kUTTypeURLBookmarkData - URLBookmarkData
  • kUTTypeUTF16ExternalPlainText - UTF16ExternalPlainText
  • kUTTypeUTF16PlainText - UTF16PlainText
  • kUTTypeUTF8PlainText - UTF8PlainText
  • kUTTypeUTF8TabSeparatedText - UTF8TabSeparatedText
  • kUTTypeVCard - VCard
  • kUTTypeVersionKey - VersionKey
  • kUTTypeVideo - Video
  • kUTTypeVolume - Volume
  • kUTTypeWaveformAudio - WaveformAudio
  • kUTTypeWebArchive - WebArchive
  • kUTTypeWindowsExecutable - WindowsExecutable
  • kUTTypeX509Certificate - X509Certificate
  • kUTTypeXML - XML
  • kUTTypeXMLPropertyList - XMLPropertyList
  • kUTTypeXPCService - XPCService
  • kUTTypeZipArchive - ZipArchive

См. следующий пример.

using MobileCoreServices;
...

NSItemProvider itemProvider = new NSItemProvider ();
itemProvider.LoadItem(UTType.PropertyList ,null, (item, err) => {
    if (err == null) {
        NSDictionary results = (NSDictionary )item;
        NSString baseURI =
results.ObjectForKey("NSExtensionJavaScriptPreprocessingResultsKey");
    }
});

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

Меры предосторожности и рекомендации

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

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

Итоги

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