Принятие Хэндофф

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

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

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

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

Принятие Хэндофф в основанных на документе приложениях

Основанные на документе приложения на iOS и OS X автоматически поддерживают Хэндофф путем автоматического создания NSUserActivity объекты для основанных на iCloud документов, если приложение Info.plist файл списка свойств включает a CFBundleDocumentTypes ключ NSUbiquitousDocumentUserActivityType, как показано в Перечислении 2-1. Значение NSUbiquitousDocumentUserActivityType строка, используемая для NSUserActivity тип действия объекта. Корреляты типа действия с ролью приложения для данного типа документа, такие как редактор или средство просмотра и тип действия могут примениться к многократным типам документов. В Перечислении 2-1 строка является указателем приложения обратного DNS с именем действия, editing, добавленный. Если они представлены таким образом, записи типа действия не должны быть повторены в NSUserActivityTypes массив приложения Info.plist.

Перечисление 2-1  запись Info.plist для Хэндофф в основанных на документе приложениях

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>NSRTFDPboardType</string>
        . . .
        <key>LSItemContentTypes</key>
        <array>
            <string>com.myCompany.rtfd</string>
        </array>
        . . .
        <key>NSUbiquitousDocumentUserActivityType</key>
        <string>com.myCompany.myEditor.editing</string>
    </dict>
</array>

URL документа помещается в userInfo словарь с NSUserActivityDocumentURLKey.

Автоматически создаваемый пользовательский объект действия доступен через документ userActivity на свойство и могут сослаться другие объекты в приложении, такие как контроллер представления в iOS или контроллер окна в OS X. Эта ссылка позволяет приложениям отследить позицию в документе, например, или отследить выбор определенных элементов. Приложение устанавливает объект действия needsSave свойство к YES каждый раз, когда это изменения состояния и сохраняет состояние в updateUserActivityState: обратный вызов.

userActivity свойство может использоваться от любого потока. Это соответствует протоколу наблюдения значения ключа (KVO) так, чтобы a userActivity объект может быть совместно использован с другими объектами, которые должны быть сохранены в синхронизации, когда документ перемещается в и из iCloud. Когда документ закрывается, пользовательские объекты действия документа лишены законной силы.

Реализующий Хэндофф непосредственно

Принятие Хэндофф в Вашем приложении требует, чтобы Вы записали код, использующий APIs в UIKit, и AppKit предусмотрел создание пользовательского объекта действия, обновление состояния объекта отследить действие и продолжение действия на другом устройстве.

Создание пользовательского объекта действия

Каждое пользовательское действие, которое может потенциально быть передано к продолжающемуся устройству, представлено пользовательским объектом действия, который инстанцируют от NSUserActivity класс. Исходное приложение создает пользовательский объект действия для каждого пользовательского действия, которое оно поддерживает. Природа тех пользовательских действий зависит от приложения. Например, веб-браузер мог бы назначить пользователя, просматривающего веб-страницу как одно действие. Приложение создает NSUserActivity экземпляр, как показано в Перечислении 2-2, каждый раз, когда пользователь открывает новое содержание отображения окна или вкладки от URL, помещая URL в объект действия userInfo словарь, вместе с позицией прокрутки страницы. Поместите этот код в объект контроллера, такой как окно или просмотрите контроллер, имеющий знание текущего состояния действия, и это может обновить данные состояния в объекте действия по мере необходимости.

Перечисление 2-2  , Создающее пользовательский объект действия

NSUserActivity* myActivity = [[NSUserActivity alloc]
                      initWithActivityType: @"com.myCompany.myBrowser.browsing"];
myActivity.userInfo = @{ ... };
myActivity.title = @"Browsing";
[myActivity becomeCurrent];

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

Указание типа действия

Идентификатор типа действия является короткой строкой, появляющейся в Вашем приложении Info.plist файл списка свойств в NSUserActivityTypes массив, перечисляющий все действие, вводит Ваши поддержки приложений. Та же строка передается при создании действия, как показано в Перечислении 2-2, где объект действия создается с типом действия com.myCompany.myBrowser.browsing, нотация обратного стиля DNS означала избегать коллизий. Когда пользователь принимает решение продолжать действие, тип действия (вместе с Командой приложения ID) определяет который приложение запуститься на приемном устройстве для продолжения действия.

Например, приложение Стиля напоминаний сериализирует список напоминания, на который смотрит пользователь. Когда пользователь щелкает по новому списку напоминания, приложение отслеживает то действие в NSUserActivityDelegate. Перечисление 2-3 показывает возможную реализацию метода, который вызывают каждый раз, когда пользователь переключается на различный список напоминания. Это приложение добавляет имя действия к идентификатору пакета приложения для создания типа действия для использования, когда это создает NSUserActivity объект.

Перечисление 2-3  , Отслеживающее пользовательское действие

    // UIResponder and NSResponder have a userActivity property
    NSUserActivity *currentActivity = [self userActivity];
 
   // Build an activity type using the app's bundle identifier
    NSString *bundleName = [[NSBundle mainBundle] bundleIdentifier];
    NSString *myActivityType =
                    [bundleName stringByAppendingString:@".selected-list"];
 
    if(![[currentActivity activityType] isEqualToString:myActivityType]) {
        [currentActivity invalidate];
 
        currentActivity = [[NSUserActivity alloc]
                                      initWithActivityType:myActivityType];
        [currentActivity setDelegate:self];
        [currentActivity setNeedsSave:YES];
 
        [self setUserActivity:currentActivity];
 
    } else {
 
        // Already tracking user activity of this type
        [currentActivity setNeedsSave:YES];
    }

Код в Перечислении 2-3 использует setNeedsSave: метод доступа отметить пользовательское действие возражает как нуждающийся против быть обновленным. Это позволяет системе объединить обновления и выполнить их лениво.

Заполнение пользовательского информационного словаря объекта действия

Объект действия имеет пользовательский информационный словарь, содержащий любые данные, необходим для вручения от действия продолжающемуся приложению. Пользовательский информационный словарь может содержать NSArray, NSData, NSDate, NSDictionary, NSNull, NSNumber, NSSet, NSString, и NSURL объекты. Система изменяет NSURL объекты, использующие file: схема и точка в документах iCloud для указания на те те же элементы в соответствующем контейнере на приемном устройстве.

Перечисление 2-4 показывает пример, создающий пользовательский объект действия для приложения, читающего документы о веб-сайте. Тип действия, набор, когда объект создается, показан в нотации обратного стиля DNS, указывающей компанию, приложение, и наконец определенное действие. webpageURL свойство представляет URL, где считанный документ расположен, и пользовательский информационный словарь заполняется с ключами и значениями, представляющими имя документа и текущий номер страницы и позицию прокрутки. В то время как читатель развивается через документ, Ваше приложение должно сохранить ту информацию текущей.

Перечисление 2-4  , Инициализирующее пользовательский информационный словарь

NSUserActivity* myActivity = [[NSUserActivity alloc]
                      initWithActivityType: @"com.myCompany.myReader.reading"];
 
// Initialize userInfo
NSURL* webpageURL = [NSURL URLWithString:@"http://www.myCompany.com"];
myActivity.userInfo = @{
             @"docName" : currentDoc,
             @"pageNumber" : self.pageNumber,
             @"scrollPosition" : self.scrollPosition
};

Принятие Хэндофф в респондентах

Можно связать объекты респондента (наследовавшийся от NSResponder на OS X или UIResponder на iOS) с данным пользовательским действием, если Вы устанавливаете действие как респондент userActivity свойство. Система автоматически сохраняет NSUserActivity объект в подходящее время, вызывая респондента updateUserActivityState: переопределение для добавления текущих данных к пользовательскому объекту действия использование объекта действия addUserInfoEntriesFromDictionary: метод.

  Респондент перечисления 2-5 переопределяет для обновления состояния действия

- (void)updateUserActivityState:(NSUserActivity *)userActivity {
    . . .
    [userActivity setTitle: self.activityTitle];
    [userActivity addUserInfoEntriesFromDictionary: self.activityUserInfo];
}

Продолжение действия

Хэндофф автоматически распространяет пользовательские действия, которые доступны, чтобы продолжаться на iOS и устройствах OS X, которые находятся в физической близости к исходному устройству и подписаны в ту же учетную запись iCloud как исходное устройство. Когда пользователь принимает решение продолжать данное действие, Хэндофф запускает надлежащее приложение и отправляет делегату приложения сообщения, определяющие, как действие возобновляется, как описано в Продолжении Действия Используя Делегата Приложения.

Реализуйте application:willContinueUserActivityWithType: метод, чтобы позволить пользователю знать действие будет продолжаться вскоре. Используйте application:continueUserActivity:restorationHandler: метод для конфигурирования приложения для продолжения действия. Системные вызовы этот метод, когда объект действия, включая действие утверждают данные в userInfo словарь, доступно продолжающемуся приложению.

Дополнительная конфигурация Вашего приложения для продолжения действия может дополнительно быть выполнена объектами, которые Вы даете блоку обработчика восстановления, передающемуся в с application:continueUserActivity:restorationHandler: сообщение. Перечисление 2-6 показывает простую реализацию этого метода.

Перечисление 2-6  , Продолжающее пользовательское действие

- (BOOL)application:(NSApplication *)application
             continueUserActivity: (NSUserActivity *)userActivity
             restorationHandler: (void (^)(NSArray *))restorationHandler {
 
    BOOL handled = NO;
 
    // Extract the payload
    NSString *type = [userActivity activityType];
    NSString *title = [userActivity title];
    NSDictionary *userInfo = [userActivity userInfo];
 
    // Assume the app delegate has a text field to display the activity information
    [appDelegateTextField setStringValue: [NSString stringWithFormat:
        @"User activity is of type %@, has title %@, and user info %@",
        type, title, userInfo]];
 
    restorationHandler(self.windowControllers);
    handled = YES;
 
    return handled;
}

В этом случае у делегата приложения есть массив NSWindowController объекты, windowControllers. Эти контроллеры окна знают, как сконфигурировать все окна приложения для возобновления действия. После передачи того массива restorationHandler блок, Хэндофф отправляет каждый из тех объектов a restoreUserActivityState: сообщение, передающее в возобновляющемся действии NSUserActivity объект. Контроллеры окна наследовались restoreUserActivityState: метод от NSResponder, и каждый объект контроллера переопределяет тот метод для конфигурирования его окна, с помощью информации в объекте действия userInfo словарь.

Для поддержки корректного отказа делегат приложения должен реализовать application:didFailToContinueUserActivityWithType:error: метод. Если Вы не реализуете тот метод, платформа приложения, тем не менее, выводит на экран диагностическую информацию, содержавшуюся в переданном - в NSError объект.

Исходное приложение к веб-браузеру Хэндофф

При использовании исходного приложения на исходном устройстве пользователь может хотеть продолжать действие на другом устройстве, не имеющем соответствующего исходного приложения. Если существует веб-страница, соответствующая действию, она может все еще быть передана. Например, видео приложения библиотеки позволяют пользователям просмотреть фильмы, доступные для просмотра, и почтовые приложения позволяют пользователям считать и составить электронную почту, и во многих случаях пользователи могут сделать то же действие, хотя веб-страница взаимодействует через интерфейс. В этом случае исходное приложение знает URL для веб-интерфейса, возможно включая синтаксис, определяющий определенное просматриваемое видео или считанное сообщение. Так, когда исходное приложение создает NSUserActivity объект, это устанавливает webpageURL свойство, и если приемное устройство не имеет приложения, поддерживающего пользовательское действие activityType, это может возобновить действие в веб-браузере по умолчанию продолжающейся платформы.

Веб-браузер на OS X, хотящем продолжать действие таким образом, должен требовать NSUserActivityTypeBrowsingWeb тип действия (путем ввода той строки в NSUserActivityTypes массив в приложении Info.plist файл списка свойств). Это гарантирует, что, если пользователь выбирает тот браузер как их браузер по умолчанию, он получает объект действия вместо Safari.

Веб-браузер к исходному приложению Хэндофф

В противоположном случае, если пользователь использует веб-браузер на исходном устройстве, и приемное устройство является устройством на iOS с исходным приложением, требующим доменной части webpageURL свойство, затем iOS запускает исходное приложение и отправляет его NSUserActivity объект с activityType значение NSUserActivityTypeBrowsingWeb. webpageURL свойство содержит URL, который пользователь посещал, в то время как userInfo словарь пуст.

Исходное приложение на приемном устройстве должно выбрать в это поведение путем требования домена в com.apple.developer.associated-domains право. Значение того права имеет формат <service>:<fully qualified domain name>, например, activitycontinuation:example.com. В этом случае служба должна быть activitycontinuation. Добавьте значение для com.apple.developer.associated-domains право в XCode в разделе Associated Domains под вкладкой Capabilities целевых настроек.

Если тот домен соответствует webpageURL свойство, Хэндофф загружает список утвержденного приложения IDs от домена. Утвержденные доменом приложения разрешены продолжать действие. На Вашем веб-сайте Вы перечисляете утвержденные приложения в названном файле JSON со знаком apple-app-site-association, например, https://example.com/apple-app-site-association. (Необходимо использовать существующее устройство, а не средство моделирования, для тестирования загрузки файла JSON.)

Файл JSON содержит словарь, указывающий список идентификаторов приложения в формате <team identifier>.<bundle identifier> во вкладке «Общие» целевых настроек, например, YWBN8XTPBJ.com.example.myApp. Перечисление 2-7 показывает пример такого файла JSON, отформатированного для чтения.

  Веб-учетные данные Серверной стороны перечисления 2-7

{
    "activitycontinuation": {
    "apps": [    "YWBN8XTPBJ.com.example.myApp",
                 "YWBN8XTPBJ.com.example.myOtherApp" ]
    }
}

Подписать файл JSON (так, чтобы это было возвращено из сервера с корректным Типом контента application/pkcs7-mime), поместите содержание в текстовый файл и подпишите его. Можно выполнить эту задачу с командами Terminal, такими как показанные в Перечислении 2-8, удалив пробел из текста для простоты манипулирования, и с помощью openssl команда с сертификатом и ключом для идентификационных данных, выпущенных центром сертификации, которому доверяет iOS (т.е. перечисленный в http://support.apple.com/kb/ht5012). Это не должны быть те же идентификационные данные, размещающие веб-учетные данные (https://example.com в перечислении в качестве примера), но это должен быть допустимый сертификат TLS для рассматриваемого доменного имени.

Перечисление 2-8  Подписывая файл учетных данных

echo '{"activitycontinuation":{"apps":["YWBN8XTPBJ.com.example.myApp",
"YWBN8XTPBJ.com.example.myOtherApp"]}}' > json.txt
 
cat json.txt | openssl smime -sign -inkey example.com.key
                             -signer example.com.pem
                             -certfile intermediate.pem
                             -noattr -nodetach
                             -outform DER > apple-app-site-association

Вывод openssl команда является файлом JSON со знаком, что Вы ставите свой веб-сайт в apple-app-site-association URL, в этом примере, https://example.com/apple-app-site-association.

Приложение может установить webpageURL свойство к любому веб-URL, но это только получает объекты действия чей webpageURL домен находится в com.apple.developer.associated-domains право. Кроме того, схема webpageURL должен быть http или https. Любая другая схема выдает исключение.

Используя потоки продолжения

Если возобновление действия требует большего количества данных, чем может быть эффективно передано начальной буквой полезная нагрузка Хэндофф, продолжающееся приложение может перезвонить к объекту действия исходного приложения открыть потоки между приложениями и передать больше данных. В этом случае исходное приложение устанавливает NSUserActivity булево свойство объекта supportsContinuationStreams к YES, устанавливает пользовательского делегата действия, затем вызывает becomeCurrent, как показано в Перечислении 2-9.

Перечисление 2-9  , Настраивающее потоки

NSUserActivity* activity = [[NSUserActivity alloc] init];
activity.title = @"Editing Mail";
activity.supportsContinuationStreams = YES;
activity.delegate = self;
[activity becomeCurrent];

На продолжающемся устройстве, после того, как пользователи указывают, что хотят возобновить действие, система запускает надлежащее приложение и начинает отправлять сообщения делегату приложения. Делегат приложения может тогда запросить, чтобы потоки назад к исходному приложению путем отправки его пользовательского действия возразили getContinuationStreamsWithCompletionHandler: сообщение, как показано в реализации переопределения в Перечислении 2-10.

Перечисление 2-10  , Запрашивающее потоки

- (BOOL)application:(UIApplication *)application
        continueUserActivity: (NSUserActivity *)userActivity
        restorationHandler: (void(^)(NSArray *restorableObjects))restorationHandler
{
    [userActivity getContinuationStreamsWithCompletionHandler:^(
                        NSInputStream *inputStream,
                        NSOutputStream *outputStream, NSError *error) {
 
        // Do something with the streams
 
        }];
 
    return YES;
}

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

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

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

Методы наиболее успешной практики

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