Xamarin.iOS でのハンドオフ

この記事では、Xamarin.iOS アプリでハンドオフを使用して、ユーザーの他のデバイスで実行されているアプリ間でユーザー アクティビティを転送する方法について説明します。

Apple は、iOS 8 と OS X Yosemite (10.10) で、ユーザーが自分のデバイスの 1 つで開始したアクティビティを、同じアプリを実行している別のデバイスまたは同じアクティビティをサポートする別のアプリに転送する共通のメカニズムを提供する Handoff を導入しました。

An example of performing a Handoff operation

この記事では、Xamarin.iOS アプリでアクティビティ共有を有効にする方法について簡単に説明し、ハンドオフ フレームワークについて詳しく説明します。

ハンドオフについて

Handoff (継続性とも呼ばれる) は、ユーザーがいずれかのデバイス (iOS または Mac) でアクティビティを開始し、その同じアクティビティを別のデバイス (ユーザーの iCloud アカウントによって特定される) で継続する方法として、iOS 8 および OS X Yosemite (10.10) で Apple によって導入されました。

iOS 9 では Handoff が強化され、新しく強化された検索機能もサポートされました。 詳細については、検索の機能強化に関するドキュメントを参照してください。

たとえば、ユーザーは自分の i電話 でメールを開始し、同じメッセージ情報をすべて入力し、カーソルを iOS に残したのと同じ場所に置いて、Mac でシームレスにメールを続行できます。

同じ チーム ID を 共有するアプリは、これらのアプリが iTunes App Store 経由で配信されるか、登録済みの開発者 (Mac、エンタープライズ、またはアドホック アプリの場合) によって署名されている限り、ハンドオフを使用してアプリ間でユーザー アクティビティを継続する資格があります。

任意 NSDocument のアプリまたは UIDocument ベースのアプリにはハンドオフサポートが自動的に組み込まれており、ハンドオフをサポートするために最小限の変更が必要です。

ユーザー アクティビティの継続

このクラスはNSUserActivity、ユーザーの別のデバイスで継続できる可能性のあるユーザーのアクティビティを定義するためのサポートを提供します (また、いくつかの小さな変更UIKitAppKitと共に)。

アクティビティを別のユーザーのデバイスに渡すには、アクティビティをインスタンスNSUserActivityにカプセル化し、現在のアクティビティとしてマークし、ペイロードセット (継続を実行するために使用されるデータ) を設定し、そのアクティビティをそのデバイスに送信する必要があります。

ハンドオフは最小限の情報を渡して、継続するアクティビティを定義します。iCloud 経由で同期されるデータ パケットが大きくなります。

受信デバイスで、ユーザーはアクティビティが継続可能であることを示す通知を受け取ります。 ユーザーが新しいデバイスでアクティビティを続行することを選択した場合は、指定されたアプリが起動され (まだ実行されていない場合)、アクティビティの NSUserActivity 再起動にペイロードが使用されます。

An overview of Continuing User Activities

同じ開発者チーム ID を共有し、特定 のアクティビティの種類 に応答するアプリのみが継続の対象となります。 アプリは、Info.plist ファイルのキーの下でサポートするNSUserActivityTypesアクティビティの種類を定義します。 この場合、継続デバイスは、チーム ID、アクティビティの種類、および必要に応じてアクティビティ タイトルに基づいて継続を実行するアプリを 選択します

受信側のアプリは、's UserInfo dictionary' からの情報をNSUserActivity使用してユーザー インターフェイスを構成し、エンド ユーザーへの移行がシームレスに表示されるように、特定のアクティビティの状態を復元します。

継続に、効率的に NSUserActivity送信できる以上の情報が必要な場合、再開アプリは発信元アプリに呼び出しを送信し、必要なデータを送信するための 1 つ以上のストリームを確立できます。 たとえば、アクティビティが複数の画像を含む大きなテキスト ドキュメントを編集していた場合、受信デバイスでアクティビティを続行するために必要な情報を転送するためにストリーミングが必要になります。 詳細については、以下の「サポート継続ストリーム」セクションを参照してください。

前述のように、 NSDocument または UIDocument ベース アプリには、ハンドオフが組み込まれているのが自動的にサポートされます。 詳細については、以下の 「ドキュメントベースのアプリ でのハンドオフのサポート」セクションを参照してください。

NSUserActivity クラス

この NSUserActivity クラスはハンドオフ交換の主オブジェクトであり、継続可能なユーザー アクティビティの状態をカプセル化するために使用されます。 アプリは、サポートされ、別の NSUserActivity デバイスで続行するアクティビティのコピーをインスタンス化します。 たとえば、ドキュメント エディターでは、現在開いている各ドキュメントのアクティビティが作成されます。 ただし、最上位のウィンドウまたはタブに表示される最前面のドキュメントのみが現在の アクティビティ であるため、継続に使用できます。

インスタンスは、そのActivityTypeインスタンスとTitleプロパティのNSUserActivity両方によって識別されます。 UserInfoディクショナリ プロパティは、アクティビティの状態に関する情報を伝達するために使用されます。 NeedsSave'のデリゲートをtrue介して状態情報を遅延読み込みする場合は、プロパティをNSUserActivity設定します。 このメソッドを使用して、アクティビティの AddUserInfoEntries 状態を維持するために必要に UserInfo 応じて、他のクライアントの新しいデータをディクショナリにマージします。

NSUserActivityDelegate クラス

これはNSUserActivityDelegate、's UserInfo dictionary' の情報をNSUserActivity最新の状態に保ち、アクティビティの現在の状態と同期するために使用されます。 システムは、アクティビティの情報を更新する必要がある場合 (別のデバイスでの継続前など)、デリゲートのメソッドを UserActivityWillSave 呼び出します。

メソッドを実装UserActivityWillSaveし、(などUserInfoTitle) に変更をNSUserActivity加えて、現在のアクティビティの状態が確実に反映されるようにする必要があります。 システムがメソッドを UserActivityWillSave 呼び出すと、 NeedsSave フラグがクリアされます。 アクティビティのいずれかのデータ プロパティを変更する場合は、もう一度設定NeedsSavetrueする必要があります。

上記のメソッドを UserActivityWillSave 使用する代わりに、必要に応じて、ユーザー アクティビティを自動的に設定 UIKit または AppKit 管理できます。 これを行うには、レスポンダー オブジェクトの UserActivity プロパティを設定し、メソッドを実装します UpdateUserActivityState 。 詳細については、後述の 「レスポンダー でのハンドオフのサポート」セクションを参照してください。

App Framework のサポート

(iOS) と (OS X) の両方UIKitで、ハンドオフ、レスポンダー (UIResponder/NSResponder)、クラスAppDelegateNSDocument組み込みサポートが提供されます。AppKit 各 OS のハンドオフの実装は若干異なりますが、基本的なメカニズムと API は同じです。

ドキュメント ベースのアプリのユーザー アクティビティ

ドキュメント ベースの iOS アプリと OS X アプリには、ハンドオフサポートが自動的に組み込まれています。 このサポートをアクティブにするには、アプリの Info.plist ファイル内の各CFBundleDocumentTypesエントリのキーと値を追加NSUbiquitousDocumentUserActivityTypeする必要があります。

このキーが存在する場合は、NSDocumentUIDocument指定された種類の iCloud ベースのドキュメントのインスタンスを自動的に作成NSUserActivityします。 アプリがサポートするドキュメントの種類ごとにアクティビティの種類を指定する必要があり、複数のドキュメントの種類で同じアクティビティの種類を使用できます。 両方 NSDocumentUIDocument 自動的にプロパティの UserInfo 値を NSUserActivity 持つ FileURL プロパティのプロパティを設定します。

OS X では、ドキュメントのウィンドウが NSUserActivity メイン ウィンドウになると、レスポンダーによってAppKit管理され、レスポンダーに関連付けられたユーザーが自動的に現在のアクティビティになります。 iOS では、管理されるオブジェクトのNSUserActivity場合は、メソッドを明示的に呼び出すかBecomeCurrent、アプリがフォアグラウンドになったときにUIViewControllerドキュメントのUserActivityプロパティを設定する必要UIKitがあります。

AppKit は、この方法で OS X で作成されたすべての UserActivity プロパティを自動的に復元します。これは、メソッドが ContinueUserActivityfalse された場合、または実装されていない場合に発生します。 この状況では、ドキュメントはメソッドを使用してOpenDocumentNSDocumentController開き、メソッド呼び出しをRestoreUserActivityState受け取ります。

詳細については、以下の 「ドキュメントベースのアプリ でのハンドオフのサポート」セクションを参照してください。

ユーザー アクティビティとレスポンダー

両方 UIKit とも、 AppKit レスポンダー オブジェクト UserActivity のプロパティとして設定した場合に、ユーザー アクティビティを自動的に管理できます。 状態が変更された場合は、レスポンダー UserActivitytrueのプロパティを NeedsSave . システムは、レスポンダーにメソッドを UserActivity 呼び出 UpdateUserActivityState して状態を更新する時間を与えた後、必要なときに自動的に保存します。

複数のレスポンダーが 1 つの NSUserActivity インスタンスを共有する場合、システムがユーザー アクティビティ オブジェクトを更新するときにコールバックを受け取ります UpdateUserActivityState 。 レスポンダーは、メソッドをAddUserInfoEntries呼び出して、この時点の現在のアクティビティの状態を反映するように 's UserInfo dictionary' を更新NSUserActivityする必要があります。 各呼び出しの UserInfo 前に UpdateUserActivityState ディクショナリがクリアされます。

レスポンダーは、アクティビティとの関連付けを解除するために、その UserActivity プロパティ nullを . アプリ フレームワーク マネージド NSUserActivity インスタンスにレスポンダーまたはドキュメントが関連付けられていない場合、自動的に無効になります。

詳細については、後述の 「レスポンダー でのハンドオフのサポート」セクションを参照してください。

ユーザー アクティビティと AppDelegate

ハンドオフ継続 AppDelegate を処理する場合、アプリはその主要なエントリ ポイントです。 ユーザーがハンドオフ通知に応答すると、適切なアプリが起動され (まだ実行されていない場合)、 WillContinueUserActivityWithType メソッドが AppDelegate 呼び出されます。 この時点で、アプリは継続が開始されていることをユーザーに通知する必要があります。

インスタンスはNSUserActivity、's ContinueUserActivity メソッドがAppDelegate呼び出されたときに配信されます。 この時点で、アプリのユーザー インターフェイスを構成し、特定のアクティビティを続行する必要があります。

詳細については、以下の 「ハンドオフ の実装」セクションを参照してください。

Xamarin アプリでのハンドオフの有効化

ハンドオフによって課されるセキュリティ要件のため、ハンドオフ フレームワークを使用する Xamarin.iOS アプリは、Apple 開発者ポータルと Xamarin.iOS プロジェクト ファイルの両方で適切に構成する必要があります。

次の操作を行います。

  1. Apple 開発者ポータルに ログインします

  2. [証明書]、[識別子]、[プロファイル] の順にクリックします

  3. まだ行っていない場合は、[識別子] をクリックしてアプリの ID (例: com.company.appname) を作成します。それ以外の場合は、既存の ID を編集します。

  4. 指定された ID に対して iCloud サービスがチェックされていることを確認します。

    Enable the iCloud service for the given ID

  5. 変更を保存します。

  6. [プロビジョニング プロファイル>開発] をクリックし、アプリ用の新しい開発プロビジョニング プロファイルを作成します。

    Create a new development provisioning profile for the app

  7. 新しいプロビジョニング プロファイルをダウンロードしてインストールするか、Xcode を使用してプロファイルをダウンロードしてインストールします。

  8. Xamarin.iOS プロジェクト オプションを編集し、先ほど作成したプロビジョニング プロファイルを使用していることを確認します。

    Select the provisioning profile just created

  9. 次に、Info.plist ファイルを編集し、プロビジョニング プロファイルの作成に使用したアプリ ID を使用していることを確認します。

    Set App ID

  10. [背景モード] セクションまでスクロールし、次の項目をチェックします。

    Enable the required background modes

  11. 変更をすべてのファイルに保存します。

これらの設定を設定すると、アプリケーションは Handoff Framework API にアクセスする準備が整いました。 プロビジョニングの詳細については、Device Provisioning and Provisioning Your App のガイドを参照してください。

ハンドオフの実装

ユーザー アクティビティは、同じ開発者チーム ID で署名され、同じアクティビティの種類をサポートするアプリ間で継続できます。 Xamarin.iOS アプリでハンドオフを実装するには、ユーザー アクティビティ オブジェクトを作成し (in またはAppKit)UIKit、オブジェクトの状態を更新してアクティビティを追跡し、受信デバイスでアクティビティを続行する必要があります。

ユーザー アクティビティの識別

ハンドオフを実装する最初の手順は、アプリがサポートするユーザー アクティビティの種類を特定し、それらのアクティビティのうち、別のデバイスでの継続の候補となるものを確認することです。 たとえば、ToDo アプリでは、アイテムの編集を 1 つの ユーザー アクティビティの種類としてサポートし、使用可能な項目リストを別のユーザー アクティビティの種類として参照できます。

アプリは、必要な数のユーザー アクティビティの種類を作成できます。1 つは、アプリが提供する任意の関数用です。 ユーザー アクティビティの種類ごとに、アプリは種類のアクティビティの開始と終了を追跡する必要があり、別のデバイスでそのタスクを続行するには、最新の状態情報をメインする必要があります。

ユーザー アクティビティは、同じチーム ID で署名された任意のアプリで続行でき、送受信アプリ間で 1 対 1 のマッピングを行う必要はありません。 たとえば、特定のアプリは、別のデバイス上の異なる個々のアプリによって消費される 4 種類のアクティビティを作成できます。 これは、アプリの Mac バージョン (多くの機能を持つ可能性がある) と iOS アプリの間で一般的に発生します。各アプリは小さく、特定のタスクに重点を置きます。

アクティビティの種類識別子の作成

アクティビティの 種類識別子 は、特定のユーザー アクティビティの種類を NSUserActivityTypes 一意に識別するために使用される、アプリの Info.plist ファイルの配列に追加された短い文字列です。 配列には、アプリがサポートするアクティビティごとに 1 つのエントリがあります。 Apple では、競合を回避するために、アクティビティの種類識別子に逆引き DNS スタイルの表記を使用することをお勧めします。 たとえば、 com.company-name.appname.activity 特定のアプリ ベースのアクティビティや com.company-name.activity 、複数のアプリで実行できるアクティビティの場合です。

アクティビティの種類識別子は、アクティビティの種類を NSUserActivity 識別するインスタンスを作成するときに使用されます。 アクティビティが別のデバイスで継続されると、アクティビティの種類 (アプリのチーム ID と共に) によって、アクティビティを続行するために起動するアプリが決まります。

例として、MonkeyBrowser というサンプル アプリを作成します (ダウンロードはこちら)。 このアプリには 4 つのタブが表示され、それぞれ異なる URL が Web ブラウザー ビューで開かれます。 ユーザーは、アプリを実行している別の iOS デバイスで任意のタブを続行できます。

この動作をサポートするために必要なアクティビティの種類識別子を作成するには、Info.plist ファイルを編集し、[ソース] ビューに切り替えます。 キーを NSUserActivityTypes 追加し、次の識別子を作成します。

The NSUserActivityTypes key and required identifiers in the plist editor

例の MonkeyBrowser アプリのタブごとに 1 つずつ、4 つの新しいアクティビティの種類識別子を作成しました。 独自のアプリを作成するときは、配列の内容を NSUserActivityTypes 、アプリがサポートするアクティビティに固有のアクティビティの種類識別子に置き換えます。

ユーザー アクティビティの変更の追跡

クラスの新しいインスタンスを NSUserActivity 作成するときに、アクティビティの状態への変更を追跡するインスタンスを指定 NSUserActivityDelegate します。 たとえば、次のコードを使用して状態の変更を追跡できます。

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

namespace MonkeyBrowse
{
    public class UserActivityDelegate : NSUserActivityDelegate
    {
        #region Constructors
        public UserActivityDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void UserActivityReceivedData (NSUserActivity userActivity, NSInputStream inputStream, NSOutputStream outputStream)
        {
            // Log
            Console.WriteLine ("User Activity Received Data: {0}", userActivity.Title);
        }

        public override void UserActivityWasContinued (NSUserActivity userActivity)
        {
            Console.WriteLine ("User Activity Was Continued: {0}", userActivity.Title);
        }

        public override void UserActivityWillSave (NSUserActivity userActivity)
        {
            Console.WriteLine ("User Activity will be Saved: {0}", userActivity.Title);
        }
        #endregion
    }
}

このメソッドは UserActivityReceivedData 、継続ストリームが送信側デバイスからデータを受信したときに呼び出されます。 詳細については、以下の「サポート継続ストリーム」セクションを参照してください。

このメソッドは UserActivityWasContinued 、別のデバイスが現在のデバイスからアクティビティを引き継ぐときに呼び出されます。 ToDo リストに新しい項目を追加するなどのアクティビティの種類によっては、アプリで送信デバイスでのアクティビティの中止が必要になる場合があります。

この UserActivityWillSave メソッドは、アクティビティに対する変更が保存され、ローカルで使用可能なデバイス間で同期される前に呼び出されます。 このメソッドを使用すると、インスタンスが送信される前に、インスタンスのプロパティに UserInfo 直前の変更を NSUserActivity 加えることができます。

NSUserActivity インスタンスの作成

アプリが別のデバイスで続行する可能性を提供する各アクティビティは、インスタンスに NSUserActivity カプセル化する必要があります。 アプリは必要な数のアクティビティを作成できます。これらのアクティビティの性質は、該当するアプリの機能と機能に依存します。 たとえば、電子メール アプリでは、新しいメッセージを作成するためのアクティビティと、メッセージを読み取るためのアクティビティを作成できます。

このサンプル アプリでは、ユーザーがタブ付き NSUserActivity Web ブラウザー ビューのいずれかに新しい URL を入力するたびに、新しい URL が作成されます。 次のコードは、特定のタブの状態を格納します。

public NSString UserActivityTab1 = new NSString ("com.xamarin.monkeybrowser.tab1");
public NSUserActivity UserActivity { get; set; }
...

UserActivity = new NSUserActivity (UserActivityTab1);
UserActivity.Title = "Weather Tab";
UserActivity.Delegate = new UserActivityDelegate ();

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

// Inform Activity that it has been updated
UserActivity.BecomeCurrent ();

上で作成したいずれかのユーザー アクティビティの種類を使用して新しい NSUserActivity アクティビティが作成され、アクティビティの人間が判読できるタイトルが提供されます。 上記で作成したインスタンスにアタッチして状態の NSUserActivityDelegate 変化を監視し、このユーザー アクティビティが現在のアクティビティであることを iOS に通知します。

UserInfo ディクショナリの設定

上で説明したように、クラスのプロパティはNSDictionaryUserInfo特定のNSUserActivityアクティビティの状態を定義するために使用されるキーと値のペアです。 格納されるUserInfo値は、次のいずれかの型である必要があります。 NSArrayNSDataNSDateNSDictionaryNSNullNSNumberNSSetNSStringNSURL NSURL iCloud ドキュメントを指すデータ値は、受信デバイス上の同じドキュメントを指すよう自動的に調整されます。

上の例では、オブジェクトを NSMutableDictionary 作成し、指定されたタブでユーザーが現在表示していた URL を指定する 1 つのキーを設定しました。ユーザー アクティビティのメソッドは AddUserInfoEntries 、受信デバイスでアクティビティを復元するために使用されるデータでアクティビティを更新するために使用されました。

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

Apple は、アクティビティが受信デバイスにタイムリーに送信されるように、送信される情報を最小限に抑えておけることをお勧めします。 ドキュメントに添付された画像を編集する必要があるなど、より大きな情報が必要な場合は、継続ストリームを使用する必要があります。 詳細については、後述の「継続ストリームのサポート」セクションを参照してください。

アクティビティの継続

ハンドオフは、発信元デバイスに物理的に近く、同じ iCloud アカウントにサインインしているローカルの iOS および OS X デバイスに、継続可能なユーザー アクティビティの可用性を自動的に通知します。 ユーザーが新しいデバイスでアクティビティを続行することを選択した場合、システムは (チーム ID とアクティビティの種類に基づいて) 適切なアプリと、その継続が必要な AppDelegate 情報を起動します。

最初に WillContinueUserActivityWithType 、このメソッドが呼び出され、アプリは継続が開始されようとしていることをユーザーに通知できます。 サンプル アプリのAppDelegate.cs ファイルで次のコードを使用して、継続の開始を処理します。

public NSString UserActivityTab1 = new NSString ("com.xamarin.monkeybrowser.tab1");
public NSString UserActivityTab2 = new NSString ("com.xamarin.monkeybrowser.tab2");
public NSString UserActivityTab3 = new NSString ("com.xamarin.monkeybrowser.tab3");
public NSString UserActivityTab4 = new NSString ("com.xamarin.monkeybrowser.tab4");
...

public FirstViewController Tab1 { get; set; }
public SecondViewController Tab2 { get; set;}
public ThirdViewController Tab3 { get; set; }
public FourthViewController Tab4 { get; set; }
...

public override bool WillContinueUserActivity (UIApplication application, string userActivityType)
{
    // Report Activity
    Console.WriteLine ("Will Continue Activity: {0}", userActivityType);

    // Take action based on the user activity type
    switch (userActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Inform view that it's going to be modified
        Tab1.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Inform view that it's going to be modified
        Tab2.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Inform view that it's going to be modified
        Tab3.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Inform view that it's going to be modified
        Tab4.PreparingToHandoff ();
        break;
    }

    // Inform system we handled this
    return true;
}

上記の例では、各ビュー コントローラーは、アクティビティ インジケーターを表示するパブリック PreparingToHandoff メソッドと、アクティビティが現在のデバイスに渡されようとしていることをユーザーに知らせるメッセージを持AppDelegateっています。 例:

private void ShowBusy(string reason) {

    // Display reason
    BusyText.Text = reason;

    //Define Animation
    UIView.BeginAnimations("Show");
    UIView.SetAnimationDuration(1.0f);

    Handoff.Alpha = 0.5f;

    //Execute Animation
    UIView.CommitAnimations();
}
...

public void PreparingToHandoff() {
    // Inform caller
    ShowBusy ("Continuing Activity...");
}

AppDelegateContinueUserActivityは、指定されたアクティビティを実際に継続するために呼び出されます。 ここでも、サンプル アプリから:

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{

    // Report Activity
    Console.WriteLine ("Continuing User Activity: {0}", userActivity.ToString());

    // Get input and output streams from the Activity
    userActivity.GetContinuationStreams ((NSInputStream arg1, NSOutputStream arg2, NSError arg3) => {
        // Send required data via the streams
        // ...
    });

    // Take action based on the Activity type
    switch (userActivity.ActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Preform handoff
        Tab1.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab1});
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Preform handoff
        Tab2.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab2});
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Preform handoff
        Tab3.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab3});
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Preform handoff
        Tab4.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab4});
        break;
    }

    // Inform system we handled this
    return true;
}

各ビュー コントローラーのパブリック PerformHandoff メソッドは、実際にハンドオフを実行し、現在のデバイスでアクティビティを復元します。 この例の場合、ユーザーが別のデバイスで閲覧していたのと同じ URL が特定のタブに表示されます。 例:

private void HideBusy() {

    //Define Animation
    UIView.BeginAnimations("Hide");
    UIView.SetAnimationDuration(1.0f);

    Handoff.Alpha = 0f;

    //Execute Animation
    UIView.CommitAnimations();
}
...

public void PerformHandoff(NSUserActivity activity) {

    // Hide busy indicator
    HideBusy ();

    // Extract URL from dictionary
    var url = activity.UserInfo ["Url"].ToString ();

    // Display value
    URL.Text = url;

    // Display the give webpage
    WebView.LoadRequest(new NSUrlRequest(NSUrl.FromString(url)));

    // Save activity
    UserActivity = activity;
    UserActivity.BecomeCurrent ();

}

この ContinueUserActivity メソッドには、 UIApplicationRestorationHandler ドキュメントベースまたはレスポンダーベースのアクティビティ再開を呼び出すことができるものがあります。 呼び出されると、復元ハンドラーにオブジェクトまたは復元可能なオブジェクトを渡す NSArray 必要があります。 次に例を示します。

completionHandler (new NSObject[]{Tab4});

渡されたオブジェクトごとに、その RestoreUserActivityState メソッドが呼び出されます。 その後、各オブジェクトはディクショナリ内のデータを UserInfo 使用して、独自の状態を復元できます。 次に例を示します。

public override void RestoreUserActivityState (NSUserActivity activity)
{
    base.RestoreUserActivityState (activity);

    // Log activity
    Console.WriteLine ("Restoring Activity {0}", activity.Title);
}

ドキュメント ベースのアプリの場合、メソッドを実装しない場合、またはメソッドが ContinueUserActivityfalseした場合、 UIKit または AppKit アクティビティを自動的に再開できます。 詳細については、以下の 「ドキュメントベースのアプリ でのハンドオフのサポート」セクションを参照してください。

ハンドオフが正常に失敗する

ハンドオフは、緩やかに接続された iOS デバイスと OS X デバイス間の情報の送信に依存するため、転送プロセスが失敗することがあります。 これらのエラーを適切に処理し、発生した状況をユーザーに通知するようにアプリを設計する必要があります。

エラーが発生した場合は、 DidFailToContinueUserActivitiy メソッドが AppDelegate 呼び出されます。 次に例を示します。

public override void DidFailToContinueUserActivitiy (UIApplication application, string userActivityType, NSError error)
{
    // Log information about the failure
    Console.WriteLine ("User Activity {0} failed to continue. Error: {1}", userActivityType, error.LocalizedDescription);
}

エラーに関する情報をユーザーに提供するには、指定された NSError 情報を使用する必要があります。

ネイティブ アプリから Web ブラウザーへのハンドオフ

ユーザーは、目的のデバイスに適切なネイティブ アプリをインストールせずにアクティビティを続行できます。 場合によっては、Web ベースのインターフェイスで必要な機能が提供される場合があり、アクティビティを継続できます。 たとえば、ユーザーの電子メール アカウントは、メッセージを作成および読み取るための Web ベースの UI を提供できます。

元のネイティブ アプリが Web インターフェイスの URL (および継続する特定の項目を識別するために必要な構文) を認識している場合は、インスタンスのプロパティでこの情報をWebpageURLNSUserActivityエンコードできます。 受信側のデバイスに、継続を処理するための適切なネイティブ アプリがインストールされていない場合は、指定された Web インターフェイスを呼び出すことができます。

Web ブラウザーからネイティブ アプリへのハンドオフ

ユーザーが送信元デバイスで Web ベースのインターフェイスを使用していて、受信側デバイスのネイティブ アプリがプロパティの doメイン 部分WebpageURLを要求している場合、システムはそのアプリを継続のハンドルとして使用します。 新しいデバイスは、アクティビティの種類BrowsingWebをマークするインスタンスを受け取りNSUserActivityWebpageURLUserInfoユーザーがアクセスしていた URL が含まれます。ディクショナリは空になります。

アプリがこの種類のハンドオフに参加するには、形式 <service>:<fully qualified domain name> (例: activity continuation:company.com) を使用して権利で com.apple.developer.associated-domains doメイン を要求する必要があります。

指定した doメイン がプロパティの値と一致WebpageURLする場合、Handoff は承認されたアプリ ID の一覧を Web サイトからダウンロードしますメイン。 Web サイトでは、apple-app-site-association という名前の署名済み JSON ファイルに承認済み ID の一覧を提供する必要があります (例: https://company.com/apple-app-site-association)。

この JSON ファイルには、フォーム <team identifier>.<bundle identifier>内のアプリ ID の一覧を指定するディクショナリが含まれています。 次に例を示します。

{
    "activitycontinuation": {
        "apps": [    "YWBN8XTPBJ.com.company.FirstApp",
            "YWBN8XTPBJ.com.company.SecondApp" ]
    }
}

JSON ファイルに署名するには (正しいContent-Typeapplication/pkcs7-mime情報が含まれるように)、ターミナル アプリとopenssl、iOS によって信頼されている証明機関によって発行された証明書とキーを含むコマンドを使用します (一覧を参照)。https://support.apple.com/kb/ht5012 次に例を示します。

echo '{"activitycontinuation":{"apps":["YWBN8XTPBJ.com.company.FirstApp",
"YWBN8XTPBJ.com.company.SecondApp"]}}' > json.txt

cat json.txt | openssl smime -sign -inkey company.com.key
-signer company.com.pem
-certfile intermediate.pem
-noattr -nodetach
-outform DER > apple-app-site-association

このコマンドはopenssl、apple-app-site-association URL で Web サイトに配置する署名付き JSON ファイルを出力します。 次に例を示します。

https://example.com/apple-app-site-association.

アプリは、メインが権利を持つWebpageURLすべてのアクティビティをcom.apple.developer.associated-domains受け取ります。 httpサポートされるのはプロトコルとhttpsプロトコルだけです。他のプロトコルでは例外が発生します。

ドキュメント ベースのアプリでのハンドオフのサポート

前述のように、iOS および OS X では、アプリの Info.plist ファイルにキーが含まれている場合、ドキュメント ベースのアプリは iCloud ベースのドキュメントのNSUbiquitousDocumentUserActivityTypeハンドオフをCFBundleDocumentTypes自動的にサポートします。 次に例を示します。

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

この例では、文字列は、アクティビティの名前が追加された逆引き DNS アプリの指定子です。 この方法で入力した場合、アクティビティの種類のエントリを Info.plist ファイルの配列でNSUserActivityTypes繰り返す必要はありません。

自動的に作成されたユーザー アクティビティ オブジェクト (ドキュメントのプロパティを通じて使用可能) は、アプリ内の他の UserActivity オブジェクトから参照でき、継続時の状態の復元に使用できます。 たとえば、アイテムの選択とドキュメントの位置を追跡します。 状態が変化するたびにこのアクティビティ NeedsSave プロパティをtrue設定し、メソッドのディクショナリを更新するUserInfoUpdateUserActivityState必要があります。

このプロパティは UserActivity 、任意のスレッドから使用でき、キー値監視 (KVO) プロトコルに準拠しているため、iCloud との間を移動するドキュメントの同期を維持するために使用できます。 UserActivityドキュメントを閉じると、プロパティは無効になります。

詳細については、ドキュメント ベースのアプリでの Apple のユーザー アクティビティ サポートに関するドキュメントを参照してください。

レスポンダーでのハンドオフのサポート

(iOS または NSResponder OS X で継承されたUIResponder) レスポンダーをアクティビティに関連付けるには、そのプロパティをUserActivity設定します。 システムは適切な時刻にプロパティを UserActivity 自動的に保存し、レスポンダーの UpdateUserActivityState メソッドを呼び出して、メソッドを使用して現在のデータを User Activity オブジェクトに AddUserInfoEntriesFromDictionary 追加します。

継続ストリームのサポート

アクティビティを続行するために必要な情報の量を、最初のハンドオフ ペイロードによって効率的に転送できない場合があります。 このような状況では、受信側のアプリは、それ自体と元のアプリの間に 1 つ以上のストリームを確立してデータを転送できます。

元のアプリは、インスタンスのプロパティを SupportsContinuationStreams 〘 にNSUserActivitytrue設定します。 次に例を示します。

// Create a new user Activity to support this tab
UserActivity = new NSUserActivity (ThisApp.UserActivityTab1){
    Title = "Weather Tab",
    SupportsContinuationStreams = true
};
UserActivity.Delegate = new UserActivityDelegate ();

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

// Inform Activity that it has been updated
UserActivity.BecomeCurrent ();

受信側のアプリは、その中AppDelegateNSUserActivityメソッドをGetContinuationStreams呼び出してストリームを確立できます。 次に例を示します。

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{

    // Report Activity
    Console.WriteLine ("Continuing User Activity: {0}", userActivity.ToString());

    // Get input and output streams from the Activity
    userActivity.GetContinuationStreams ((NSInputStream arg1, NSOutputStream arg2, NSError arg3) => {
        // Send required data via the streams
        // ...
    });

    // Take action based on the Activity type
    switch (userActivity.ActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Preform handoff
        Tab1.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab1});
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Preform handoff
        Tab2.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab2});
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Preform handoff
        Tab3.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab3});
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Preform handoff
        Tab4.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab4});
        break;
    }

    // Inform system we handled this
    return true;
}

元のデバイスでは、ユーザー アクティビティ デリゲートは、再開デバイスでユーザー アクティビティを続行するように要求されたデータを提供するメソッドを呼び出 DidReceiveInputStream すことによってストリームを受信します。

a NSInputStream を使用してストリーム データへの読み取り専用アクセスを提供し、書き込み専用アクセスを NSOutputStream 提供します。 ストリームは要求と応答の方法で使用する必要があります。この方法では、受信アプリはより多くのデータを要求し、元のアプリが提供します。 そのため、送信元デバイスの出力ストリームに書き込まれたデータは、継続デバイス上の入力ストリームから読み取られます。その逆も同様です。

継続ストリームが必要な場合でも、2 つのアプリ間の通信は最小限に抑える必要があります。

詳細については、Apple の継続ストリームの使用に関するドキュメントを参照してください。

ハンドオフのベスト プラクティス

ハンドオフによるユーザー アクティビティのシームレスな継続の正常な実装には、関連するすべてのコンポーネントが含まれているため、慎重な設計が必要です。 Apple では、ハンドオフ対応アプリに次のベスト プラクティスを採用することをお勧めします。

  • アクティビティの状態を引き続き関連付けるために、可能な限り最小のペイロードを要求するようにユーザー アクティビティを設計します。 ペイロードが大きいほど、継続の開始にかかる時間が長くなります。
  • 継続を成功させるために大量のデータを転送する必要がある場合は、構成とネットワークのオーバーヘッドに関連するコストを考慮してください。
  • 大規模な Mac アプリでは、iOS デバイス上の複数の小規模なタスク固有のアプリによって処理されるユーザー アクティビティを作成するのが一般的です。 さまざまなアプリと OS のバージョンは、正常に連携するか、正常に失敗するように設計する必要があります。
  • アクティビティの種類を指定するときは、逆引き DNS 表記を使用して競合を回避します。 アクティビティが特定のアプリに固有の場合は、その名前を型定義に含める必要があります (たとえば com.myCompany.myEditor.editing)。 アクティビティが複数のアプリで機能する場合は、定義からアプリ名を削除します (たとえば com.myCompany.editing)。
  • アプリでユーザー アクティビティ (NSUserActivity) の状態を更新する必要がある場合は、プロパティtrueNeedsSave . 必要に応じて、Handoff はデリゲートの UserActivityWillSave メソッドを呼び出して、必要に応じてディクショナリを UserInfo 更新できるようにします。
  • ハンドオフ プロセスは受信側デバイスですぐに初期化されない可能性があるため、'sWillContinueUserActivity' を実装AppDelegateし、継続が開始しようとしていることをユーザーに通知する必要があります。

ハンドオフ アプリの例

Xamarin.iOS アプリで Handoff を使用する例として、このガイドに MonkeyBrowser サンプル アプリが含まれています アプリには、ユーザーが Web を閲覧するために使用できる 4 つのタブがあり、それぞれに特定のアクティビティの種類 (Weather、Favorite、Coffee Break、Work) があります。

任意のタブで、ユーザーが新しい URL を入力して [移動] ボタンをタップすると、ユーザーが現在参照している URL を含むタブに対して新しい NSUserActivity URL が作成されます。

Example Handoff App

他のユーザーのデバイスに MonkeyBrowser アプリがインストールされていて、同じユーザー アカウントを使用して iCloud にサインインしている場合、同じネットワーク上にあり、上記のデバイスに近接している場合は、ホーム画面 (左下隅) にハンドオフ アクティビティが表示されます。

The Handoff Activity displayed on the home screen in the lower left hand corner

ユーザーがハンドオフ アイコンを上にドラッグすると、アプリが起動し、指定された NSUserActivity ユーザー アクティビティが新しいデバイスで続行されます。

The User Activity continued on the new device

ユーザー アクティビティが別の NSUserActivity Apple デバイスに正常に送信されると、送信側デバイスはメソッドの呼び出しをUserActivityWasContinuedNSUserActivityDelegate受け取り、ユーザー アクティビティが別のデバイスに正常に転送されたことを通知します。

まとめ

この記事では、ユーザーの複数の Apple デバイス間でユーザー アクティビティを続行するために使用されるハンドオフ フレームワークの概要について説明しました。 次に、Xamarin.iOS アプリでハンドオフを有効にして実装する方法について説明しました。 最後に、利用可能なさまざまな種類のハンドオフ継続とハンドオフのベスト プラクティスについて説明しました。