Учебник по расширениям приложений iOS 8
Опубликовано: 2022-03-11Раньше мало кто пробовал (взгляните на это), но именно Apple с первым iPhone определила, как должны выглядеть смартфон и мобильная ОС. Apple совершила невероятный прорыв в аппаратном обеспечении и пользовательском опыте. Однако мы часто забываем, что они также устанавливают стандарты того, как должна работать мобильная ОС и как должны создаваться приложения для смартфонов.
Возведение между приложениями бетонных стен, делающих их полностью изолированными и не знающими друг о друге, было лучшим способом обеспечить их безопасность и защитить их данные. Все действия тщательно отслеживались iOS, и было лишь несколько действий, которые приложение могло бы выполнить за пределами своей области.
«Воздержание — лучшая защита!» - а где же в этом веселье?
Им потребовалось некоторое время; слишком долго, если вы спросите меня, но с iOS 8 Apple решила немного повеселиться. iOS 8 представила новую концепцию под названием «Расширения приложений». Эта новая функция не разрушила стены между приложениями, но открыла несколько дверей, обеспечивая нежный, но ощутимый контакт между некоторыми приложениями. Последнее обновление дало разработчикам iOS возможность настраивать экосистему iOS, и мы с нетерпением ждем открытия этого пути.
Что такое расширения приложений iOS 8 и как они работают?
Проще говоря, iOS 8 App Extensions предоставляет новый метод взаимодействия с вашим приложением, не запуская его и не показывая на экране.
Как и ожидалось, Apple позаботилась о том, чтобы они оставались в курсе всего, поэтому есть всего несколько новых точек входа, которые может предоставить ваше приложение:
- Сегодня (также называемый виджетом) — расширение, отображаемое в представлении «Сегодня» Центра уведомлений, показывает краткую информацию и позволяет выполнять быстрые задачи.
- Поделиться — расширение, которое позволяет вашему приложению делиться контентом с пользователями в социальных сетях и других сервисах обмена.
- Действие — расширение, которое позволяет создавать настраиваемые кнопки действий на листе действий, чтобы пользователи могли просматривать или преобразовывать содержимое, созданное в ведущем приложении.
- Редактирование фотографий — расширение, которое позволяет пользователям редактировать фото или видео в приложении «Фотографии».
- Поставщик документов — расширение, используемое для предоставления другим приложениям доступа к документам, управляемым вашим приложением.
- Custom Keyboard — расширение, заменяющее системную клавиатуру.
Расширения приложений не являются самостоятельными приложениями. Они предоставляют расширенные функциональные возможности приложения (доступ к которым можно получить из других приложений, называемых хост-приложениями), которые должны быть эффективными и сосредоточены на одной задаче. У них есть собственный двоичный файл, собственная подпись кода и собственный набор элементов, но они доставляются через App Store как часть содержащего двоичный файл приложения. Одно (содержащее) приложение может иметь более одного расширения. Как только пользователь установит приложение с расширениями, они станут доступны в iOS.
Давайте рассмотрим пример: пользователь находит изображение с помощью Safari, нажимает кнопку «Поделиться» и выбирает расширение вашего приложения для обмена. Safari «разговаривает» с платформой iOS Social, которая загружает и представляет расширение. Код расширения запускается, передает данные, используя созданные системой каналы связи, и как только задача выполнена, Safari отключает представление расширения. Вскоре после этого система завершает процесс, и ваше приложение больше не отображается на экране. Тем не менее, он завершил функцию обмена изображениями.
iOS, использующая межпроцессное взаимодействие, отвечает за совместную работу хост-приложения и расширения приложения. Разработчики используют высокоуровневые API, предоставляемые точкой расширения и системой, поэтому им никогда не приходится беспокоиться о базовых механизмах связи.
Жизненный цикл
У расширений приложений другой жизненный цикл, чем у приложений iOS. Хост-приложение запускает жизненный цикл расширения в ответ на действие пользователя. Затем система создает экземпляр расширения приложения и устанавливает канал связи между ними. Представление расширения отображается в контексте ведущего приложения с использованием элементов, полученных в запросе ведущего приложения. После отображения представления расширения пользователь может взаимодействовать с ним. В ответ на действие пользователя расширение завершает запрос ведущего приложения, немедленно выполняя/отменяя задачу или, при необходимости, инициируя фоновый процесс для ее выполнения. Сразу после этого ведущее приложение отключает представление расширения, и пользователь возвращается к своему предыдущему контексту в ведущем приложении. Результаты выполнения этого процесса могут быть возвращены в ведущее приложение после его завершения. Расширение обычно завершается вскоре после завершения запроса, полученного от хост-приложения (или запускает фоновый процесс для его выполнения).
Система открывает расширение действия пользователя из ведущего приложения, расширение отображает пользовательский интерфейс, выполняет некоторую работу и возвращает данные в ведущее приложение (если это соответствует типу расширения). Содержащее приложение даже не работает, пока работает его расширение.
Создание расширения приложения — практический пример с использованием расширения «Сегодня»
Расширения «Сегодня», также называемые виджетами , расположены в представлении «Сегодня» Центра уведомлений. Это отличный способ представить пользователю актуальный контент (например, показать погодные условия) или выполнить быстрые задачи (например, отметить то, что было сделано, в виджете приложения со списком дел). Я должен указать здесь, что ввод с клавиатуры не поддерживается .
Давайте создадим расширение Today, которое будет отображать самую актуальную информацию из нашего приложения (код на GitHub). Чтобы запустить этот код, убедитесь, что вы (пере)настроили группу приложений для проекта (выберите свою команду разработчиков, имейте в виду, что имя группы приложений должно быть уникальным, и следуйте инструкциям Xcode).
Создание нового виджета
Как мы уже говорили, расширения приложений не являются самостоятельными приложениями. Нам нужно содержащее приложение, для которого мы создадим расширение приложения. После того, как у нас есть содержащее приложение, мы решили добавить новую цель, перейдя в «Файл» -> «Создать» -> «Цель» в Xcode. Отсюда мы выбираем шаблон для нашей новой цели, чтобы добавить расширение «Сегодня».
На следующем шаге мы можем выбрать название нашего продукта. Это имя будет отображаться в представлении «Сегодня» Центра уведомлений. На этом этапе также есть возможность выбрать язык между Swift и Objective-C. По завершении этих шагов Xcode создает шаблон Today, который предоставляет файлы заголовка и реализации по умолчанию для основного класса (с именем TodayViewController
) с файлом Info.plist
и файлом интерфейса (файл раскадровки или .xib). Info.plist
по умолчанию выглядит так:
<key>NSExtension</key> <dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.widget-extension</string> </dict>
Если вы не хотите использовать раскадровку, предоставленную шаблоном, удалите ключ NSExtensionMainStoryboard
и добавьте ключ NSExtensionPrincipalClass
с именем вашего контроллера представления в качестве значения.
Виджет «Сегодня» должен:
- убедитесь, что контент всегда выглядит актуальным
- адекватно реагировать на взаимодействие с пользователем
- работать хорошо (виджеты iOS должны разумно использовать память, иначе они будут остановлены системой)
Совместное использование данных и общий контейнер
Расширение приложения и содержащее его приложение имеют доступ к общим данным в своем частном общем контейнере, что является способом косвенной связи между содержащим приложением и расширением.
Разве вам не нравится, как Apple делает эти вещи такими «простыми»? :)
Совместное использование данных через NSUserDefaults
является простым и распространенным вариантом использования. По умолчанию расширение и содержащее его приложение используют отдельные наборы данных NSUserDefaults
и не могут получить доступ к контейнерам друг друга. Чтобы изменить это поведение, iOS представила группы приложений . После включения групп приложений в содержащем приложении и расширении вместо использования [NSUserDefaults standardUserDefaults]
используйте [[NSUserDefaults alloc] initWithSuiteName:@"group.yourAppGroupName"]
для доступа к тому же общему контейнеру.
Обновление виджета
Чтобы содержимое всегда было актуальным, расширение «Сегодня» предоставляет API для управления состоянием виджета и обработки обновлений содержимого. Система время от времени делает снимки представления виджета, поэтому, когда виджет становится видимым, отображается самый последний снимок до тех пор, пока он не будет заменен активной версией представления. Соответствие протоколу NCWidgetProviding
важно для обновления состояния виджета перед созданием моментального снимка. Как только виджет получает widgetPerformUpdateWithCompletionHandler:
представление виджета должно быть обновлено самым последним содержимым, а обработчик завершения должен быть вызван с одной из следующих констант для описания результата обновления:

-
NCUpdateResultNewData
— новое содержимое требует перерисовки представления. -
NCUpdateResultNoDate
— виджет не требует обновления -
NCUpdateResultFailed
— в процессе обновления произошла ошибка.
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { // Perform any setup necessary in order to update the view. // If an error is encountered, use NCUpdateResultFailed // If there's no update required, use NCUpdateResultNoData // If there's an update, use NCUpdateResultNewData [self updateTableView]; completionHandler(NCUpdateResultNewData); }
Управление видимостью виджета
Чтобы управлять отображением виджета, используйте метод setHasContent:forWidgetWithBundleIdentifier:
из класса NCWidgetController
. Этот метод позволит вам указать состояние содержимого виджета. Его можно вызвать из виджета или из содержащего его приложения (если оно активно). Вы можете передать этому методу флаг NO
или YES
, определяющий, готово содержимое виджета или нет. Если содержимое не готово, iOS не будет отображать ваш виджет при открытии представления «Сегодня».
NCWidgetController *widgetController = [[NCWidgetController alloc] init]; [widgetController setHasContent:YES forWidgetWithBundleIdentifier:@"com.your-company.your-app.your-widget"];
Открытие содержащего приложение из виджета
Виджет «Сегодня» — единственное расширение, которое может запросить открытие содержащего его приложения, вызвав метод openURL:completionHandler:
Чтобы убедиться, что содержащее приложение открывается так, как это имеет смысл в контексте текущей задачи пользователя, необходимо определить настраиваемую схему URL-адресов (которую могут использовать как виджет, так и содержащее приложение).
[self.extensionContext openURL:[NSURL URLWithString:@"customURLsheme://URLpath"] completionHandler:nil];
Соображения пользовательского интерфейса
При разработке виджета используйте класс UIVisualEffectView
, помня, что представления, которые должны быть размытыми/яркими, должны быть добавлены в contentView
а не непосредственно в UIVisualEffectView
. Виджеты (соответствующие протоколу NCWidgetProviding
) должны загружать кешированные состояния в viewWillAppear:
чтобы соответствовать состоянию представления из последнего viewWillDisappear:
и затем плавно переходить к новым данным, когда они поступают, чего нельзя сказать о обычном представлении. контроллер (пользовательский интерфейс настраивается в viewDidLoad
и обрабатывает анимацию и загрузку данных в viewWillAppear
). Виджеты должны быть предназначены для выполнения задачи или открытия содержащего их приложения одним касанием. Ввод с клавиатуры недоступен в виджете. Это означает, что любой пользовательский интерфейс, требующий ввода текста, не должен использоваться.
Добавление прокрутки в виджет, как вертикальной, так и горизонтальной, невозможно. Или, точнее, добавление прокрутки возможно, но прокрутка работать не будет. Жест горизонтальной прокрутки в представлении прокрутки в расширении «Сегодня» будет перехвачен центром уведомлений, что приведет к прокрутке от «Сегодня» к центру уведомлений. Вертикальная прокрутка представления прокрутки внутри расширения «Сегодня» будет прервана прокруткой представления «Сегодня».
Технические примечания
Здесь я укажу на некоторые важные вещи, о которых следует помнить при создании расширения приложения.
Функции, общие для всех расширений
Следующие пункты верны для всех расширений:
Объект sharedApplication запрещен : расширения приложения не могут получить доступ к объекту sharedApplication или использовать какие-либо методы, связанные с этим объектом.
Камера и микрофон запрещены : расширения приложений не могут получить доступ к камере или микрофону на устройстве (но это относится не ко всем элементам оборудования). Это связано с недоступностью некоторых API. Чтобы получить доступ к некоторым аппаратным элементам в расширении приложения, вам необходимо проверить, доступен ли его API для расширений приложения или нет (с проверкой доступности API, описанной выше).
Большинство фоновых задач недоступны: расширения приложений не могут выполнять длительные фоновые задачи, за исключением инициации загрузки или скачивания, что обсуждается ниже.
AirDrop запрещен : расширения приложений не могут получать (но могут отправлять) данные с помощью AirDrop.
Загрузка/загрузка в фоновом режиме
Единственная задача, которую можно выполнять в фоновом режиме, — это загрузка/выгрузка с использованием NSURLSession object
.
После запуска задачи загрузки/выгрузки расширение может выполнить запрос ведущего приложения и быть завершено без какого-либо влияния на результат задачи. Если расширение не запущено во время завершения фоновой задачи, система запускает содержащее приложение в фоновом режиме и вызывается метод делегата application:handleEventsForBackgroundURLSession:completionHandler:
Приложение, расширение которого инициирует фоновую задачу NSURLSession
, должно иметь настроенный общий контейнер, к которому могут получить доступ как содержащее приложение, так и его расширение.
Обязательно создайте разные фоновые сеансы для содержащего приложения и каждого из его расширений приложения (каждый фоновый сеанс должен иметь уникальный идентификатор). Это важно, потому что только один процесс может одновременно использовать фоновый сеанс.
Действие против Поделиться
Различия между расширениями Action и Share не совсем ясны с точки зрения программиста, потому что на практике они очень похожи. Шаблон Xcode для цели расширения общего доступа использует SLComposeServiceViewController
, который предоставляет стандартный пользовательский интерфейс представления создания, который вы можете использовать для обмена в социальных сетях, но это не обязательно. Расширение общего доступа также может наследоваться непосредственно от UIViewController для полностью индивидуального дизайна, точно так же, как расширение Action может наследоваться от SLComposeServiceViewController
.
Различия между этими двумя типами расширений заключаются в том, как они предназначены для использования. С помощью расширения Action вы можете создать расширение без собственного пользовательского интерфейса (например, расширение, используемое для перевода выделенного текста и возврата перевода в ведущее приложение). Расширение Share позволяет вам делиться комментариями, фотографиями, видео, аудио, ссылками и многим другим прямо из хост-приложения. UIActivityViewController
управляет расширениями Action и Share, где расширения Share представлены в виде цветных значков в верхней строке, а расширения действий представлены в виде монохромных значков в нижней строке (рис. 2.1).
Запрещенные API
API, помеченные в файлах заголовков макросом NS_EXTENSION_UNAVAILABLE
или аналогичным макросом для недоступности, нельзя использовать (например, платформы пользовательского интерфейса HealthKit и EventKit в iOS 8 недоступны для использования в любом расширении приложения).
Если вы делитесь кодом между приложением и расширением, вы должны помнить, что даже ссылка на API, который не разрешен для расширения приложения, приведет к отклонению вашего приложения из App Store. Вы можете справиться с этим путем рефакторинга общих классов в иерархии с общим родителем и разными подклассами для разных целей. Другой способ — использовать препроцессор с помощью проверок #ifdef
. Поскольку до сих пор нет встроенного целевого условного выражения, вам нужно создать свое собственное.
Еще один хороший способ сделать это — создать собственный встроенный фреймворк. Просто убедитесь, что он не будет содержать никаких API, недоступных для расширений. Чтобы настроить расширение приложения для использования встроенной платформы, перейдите к параметрам сборки цели и установите для параметра «Требовать только безопасный API-расширение приложения» значение «Да». При настройке проекта Xcode на этапе сборки «Копировать файлы» в качестве места назначения для встроенной платформы необходимо выбрать «Фреймворки». Если вы выберете пункт назначения «SharedFrameworks», ваша заявка будет отклонена App Store.
Примечание об обратной совместимости
Хотя расширения приложений доступны только с iOS 8, вы можете сделать содержащее их приложение доступным для предыдущих версий iOS.
Соответствие человеческому интерфейсу Apple
При разработке расширения приложения помните о Руководстве Apple по человеческому интерфейсу iOS. Вы должны убедиться, что ваше расширение приложения является универсальным, независимо от того, какое устройство поддерживает ваше содержащее приложение. Чтобы расширение приложения было универсальным, используйте параметр сборки целевого семейства устройств в Xcode, указав значение «iPhone/iPad» (иногда называемое универсальным).
Заключение
Расширения приложений, безусловно, оказывают наиболее заметное влияние на iOS 8. Поскольку 79% устройств уже используют iOS 8 (по данным App Store на 13 апреля 2015 г.), расширения приложений — это потрясающие функции, которыми приложения должны воспользоваться. Сочетая ограничения API и способ обмена данными между расширениями и содержащим их приложением, кажется, что Apple удалось решить одну из самых больших жалоб на платформу, не ставя под угрозу ее модель безопасности. Сторонние приложения по-прежнему не могут напрямую обмениваться данными друг с другом. Хотя это очень новая концепция, она выглядит очень многообещающе.