Xamarin.iOS でのハンドオフ
この記事では、Xamarin.iOS アプリでハンドオフを使用して、ユーザーの他のデバイスで実行されているアプリ間でユーザー アクティビティを転送する方法について説明します。
Apple は、iOS 8 と OS X Yosemite (10.10) で、ユーザーが自分のデバイスの 1 つで開始したアクティビティを、同じアプリを実行している別のデバイスまたは同じアクティビティをサポートする別のアプリに転送する共通のメカニズムを提供する Handoff を導入しました。
この記事では、Xamarin.iOS アプリでアクティビティ共有を有効にする方法について簡単に説明し、ハンドオフ フレームワークについて詳しく説明します:
ハンドオフについて
Handoff (継続性とも呼ばれる) は、ユーザーがいずれかのデバイス (iOS または Mac) でアクティビティを開始し、その同じアクティビティを別のデバイス (ユーザーの iCloud アカウントによって特定される) で継続する方法として、iOS 8 および OS X Yosemite (10.10) で Apple によって導入されました。
iOS 9 では Handoff が強化され、新しく強化された検索機能もサポートされました。 詳細については、検索の機能強化に関するドキュメントを参照してください。
たとえば、ユーザーは自分の iPhone でメールを開始し、同じメッセージ情報がすべて入力された状態で、カーソルを iOS に残したのと同じ場所に置いて、Mac でシームレスにメールを続行できます。
同じ チーム ID を共有するアプリは、これらのアプリが iTunes App Store 経由で配信されるか、登録済みの開発者 (Mac、エンタープライズ、またはアドホック アプリの場合) によって署名されている限り、ハンドオフを使用してアプリ間でユーザー アクティビティを継続する資格があります。
任意 NSDocument
のアプリまたは UIDocument
ベースのアプリにはハンドオフ サポートが自動的に組み込まれており、ハンドオフをサポートするために最小限の変更が必要です。
ユーザー アクティビティの継続
NSUserActivity
クラス (UIKit
と AppKit
に対するいくつかの小さな変更と共に) は、ユーザーの別のデバイスで継続できる可能性のあるユーザーのアクティビティを定義するためのサポートを提供します。
アクティビティを別のユーザーのデバイスに渡すには、アクティビティをインスタンス NSUserActivity
にカプセル化し、現在のアクティビティとしてマークし、ペイロードセット (継続を実行するために使用されるデータ) を設定し、そのアクティビティをそのデバイスに送信する必要があります。
ハンドオフでは、継続するアクティビティをていぎするための最小限の情報が渡され、より大きなデータは iCloud 経由で同期されます。
受信デバイスで、ユーザーはアクティビティが継続可能であることを示す通知を受け取ります。 ユーザーが新しいデバイスでアクティビティを続行することを選択した場合は、指定されたアプリが起動され (まだ実行されていない場合)、NSUserActivity
からのペイロードがアクティビティの再起動に使用されます。
同じ開発者チーム ID を共有し、特定のアクティビティの種類に応答するアプリのみが継続の対象となります。 アプリは、Info.plist ファイルの NSUserActivityTypes
キーの下でサポートするアクティビティの種類を定義します。 この場合、継続デバイスは、チーム ID、アクティビティの種類、および必要に応じてアクティビティ タイトルに基づいて継続を実行するアプリを選択します。
受信側のアプリは、NSUserActivity
の UserInfo
辞書からの情報を使用してユーザー インターフェイスを構成し、エンド ユーザーへの移行がシームレスに表示されるように、特定のアクティビティの状態を復元します。
継続に、NSUserActivity
経由で効率的に送信できる以上の情報が必要な場合、再開アプリは発信元アプリに呼び出しを送信し、必要なデータを送信するための 1 つ以上のストリームを確立できます。 たとえば、アクティビティが複数の画像を含む大きなテキスト ドキュメントを編集していた場合、受信デバイスでアクティビティを続行するために必要な情報を転送するためにストリーミングが必要になります。 詳細については、以下の「継続ストリームのサポート」セクションを参照してください。
前述のように、NSDocument
または UIDocument
ベースのアプリには、ハンドオフ サポートが自動的に組み込まれています。 詳細については、以下の「ドキュメントベースのアプリでのハンドオフのサポート」セクションを参照してください。
NSUserActivity クラス
NSUserActivity
クラスはハンドオフ交換の主オブジェクトであり、継続可能なユーザー アクティビティの状態をカプセル化するために使用されます。 アプリは、サポートされ、別のデバイスで続行するアクティビティの NSUserActivity
のコピーをインスタンス化します。 たとえば、ドキュメント エディターでは、現在開いている各ドキュメントのアクティビティが作成されます。 ただし、最上位のウィンドウまたはタブに表示される最前面のドキュメントのみが現在のアクティビティであるため、継続に使用できます。
NSUserActivity
のインスタンスは、その ActivityType
プロパティと Title
プロパティの両方によって識別されます。 UserInfo
辞書プロパティは、アクティビティの状態に関する情報を伝達するために使用されます。 NSUserActivity
のデリゲートを介して状態情報を遅延読み込みする場合は、NeedsSave
プロパティを true
に設定します。 AddUserInfoEntries
メソッドを使用して、アクティビティの状態を維持するために必要に応じて、他のクライアントの新しいデータを UserInfo
辞書にマージします。
NSUserActivityDelegate クラス
NSUserActivityDelegate
は、NSUserActivity
の UserInfo
辞書の情報を最新の状態に保ち、アクティビティの現在の状態と同期するために使用されます。 システムは、アクティビティの情報を更新する必要がある場合 (別のデバイスでの継続前など)、デリゲートの UserActivityWillSave
メソッドを呼び出します。
UserActivityWillSave
メソッドを実装し、(UserInfo
、Title
など) NSUserActivity
に変更を加えて、現在のアクティビティの状態が確実に反映されるようにする必要があります。 システムが UserActivityWillSave
メソッドを呼び出すと、NeedsSave
フラグがクリアされます。 アクティビティのいずれかのデータ プロパティを変更する場合は、もう一度 NeedsSave
を true
に設定する必要があります。
上記の UserActivityWillSave
メソッドを使用する代わりに、必要に応じて、ユーザー アクティビティを自動的に UIKit
または AppKit
管理できます。 これを行うには、レスポンダー オブジェクトの UserActivity
プロパティを設定し、 UpdateUserActivityState
メソッドを実装します。 詳細については、後述の「レスポンダーでのハンドオフのサポート」セクションを参照してください。
アプリ フレームワークのサポート
UIKit
(iOS) と AppKit
(OS X) の両方で、NSDocument
レスポンダー (UIResponder
/NSResponder
)、および AppDelegate
クラスでのハンドオフのサポートが組み込まれています。 各 OS のハンドオフの実装は若干異なりますが、基本的なメカニズムと API は同じです。
ドキュメント ベースのアプリのユーザー アクティビティ
ドキュメント ベースの iOS アプリと OS X アプリには、ハンドオフサポートが自動的に組み込まれています。 このサポートをアクティブにするには、アプリの Info.plist ファイル内の各 CFBundleDocumentTypes
エントリに NSUbiquitousDocumentUserActivityType
キーと値を追加する必要があります。
このキーが存在する場合、NSDocument
と UIDocument
の両方で、指定した種類の iCloud ベースのドキュメントの NSUserActivity
インスタンスが自動的に作成されます。 アプリがサポートするドキュメントの種類ごとにアクティビティの種類を指定する必要があり、複数のドキュメントの種類で同じアクティビティの種類を使用できます。 NSDocument
と UIDocument
の両方で、NSUserActivity
の UserInfo
プロパティに FileURL
プロパティの値が自動的に設定されます。
OS X では、 AppKit
によって管理され、レスポンダーに関連付けられている NSUserActivity
は、ドキュメントのウィンドウがメイン ウィンドウになると、自動的に現在のアクティビティになります。 iOS では、UIKit
によって管理される NSUserActivity
オブジェクトの場合、BecomeCurrent
メソッドを明示的に呼び出すか、アプリが前面になったときにドキュメントの UserActivity
プロパティを UIViewController
に設定する必要があります。
AppKit
は、OS X でこのように作成されたすべての UserActivity
プロパティを自動的に復元します。これは、ContinueUserActivity
メソッドが false
を返す場合、または実装されていない場合に発生します。 この場合、ドキュメントは NSDocumentController
の OpenDocument
メソッドで開き、RestoreUserActivityState
メソッド呼び出しを受け取ります。
詳細については、以下の「ドキュメントベースのアプリでのハンドオフのサポート」セクションを参照してください。
ユーザー アクティビティとレスポンダー
レスポンダー オブジェクトの UserActivity
プロパティとして設定すると、UIKit
と AppKit
の両方でユーザー アクティビティを自動的に管理できます。 状態が変更された場合は、レスポンダーの UserActivity
の NeedsSave
プロパティを true
に設定する必要があります。 システムは、UpdateUserActivityState
メソッドを呼び出してレスポンダーに状態を更新する時間を与えた後、必要に応じて UserActivity
を自動的に保存します。
複数のレスポンダーが 1 つの NSUserActivity
インスタンスを共有する場合、システムがユーザー アクティビティ オブジェクトを更新すると、UpdateUserActivityState
コールバックを受け取ります。 レスポンダーは、 AddUserInfoEntries
メソッドを呼び出して、NSUserActivity
の UserInfo
辞書を更新して、この時点での現在のアクティビティの状態を反映する必要があります。 UserInfo
辞書は、各 UpdateUserActivityState
呼び出しの前にクリアされます。
レスポンダーは、アクティビティとの関連付けを解除するために、UserActivity
プロパティを null
に設定できます。 アプリ フレームワークのマネージド NSUserActivity
インスタンスにレスポンダーやドキュメントが関連付けられていない場合、自動的に無効になります。
詳細については、後述の「レスポンダーでのハンドオフのサポート」セクションを参照してください。
ユーザー アクティビティと AppDelegate
ハンドオフ継続を処理する場合、アプリの AppDelegate
が主要なエントリ ポイントになります。 ユーザーがハンドオフ通知に応答すると、適切なアプリが起動され (まだ実行されていない場合)、AppDelegate
の WillContinueUserActivityWithType
メソッドが呼び出されます。 この時点で、アプリは継続が開始されていることをユーザーに通知する必要があります。
NSUserActivity
インスタンスは、AppDelegate
の ContinueUserActivity
メソッドが呼び出されたときに配信されます。 この時点で、アプリのユーザー インターフェイスを構成し、特定のアクティビティを続行する必要があります。
詳細については、以下の「ハンドオフの実装」セクションを参照してください。
Xamarin アプリでのハンドオフの有効化
ハンドオフによって課されるセキュリティ要件のため、ハンドオフ フレームワークを使用する Xamarin.iOS アプリは、Apple Developer ポータルと Xamarin.iOS プロジェクト ファイルの両方で適切に構成する必要があります。
次の操作を行います。
Apple Developer ポータルにログインします。
[証明書、識別子、プロファイル] をクリックします。
まだ行っていない場合は、[識別子] をクリックし、アプリの ID (
com.company.appname
など) を作成します。それ以外の場合は、既存の ID を編集します。iCloud サービスで、指定された ID がチェックされていることを確認します:
変更を保存。
[プロビジョニング プロファイル]>[開発] をクリックし、アプリ用の新しい開発プロビジョニング プロファイルを作成します:
新しいプロビジョニング プロファイルをダウンロードしてインストールするか、Xcode を使用してプロファイルをダウンロードしてインストールします。
Xamarin.iOS プロジェクト オプションを編集し、先ほど作成したプロビジョニング プロファイルを使用していることを確認します:
次に、Info.plist ファイルを編集し、プロビジョニング プロファイルの作成に使用したアプリ ID を使用していることを確認します:
[バックグラウンド モード] セクションまでスクロールし、次の項目を確認します:
変更をすべてのファイルに保存します。
これらの設定を設定すると、アプリケーションは Handoff Framework API にアクセスする準備が整いました。 プロビジョニングの詳細については、「デバイス プロビジョニング」ガイド と「アプリのプロビジョニング」ガイドを参照してください。
ハンドオフの実装
ユーザー アクティビティは、同じ開発者チーム ID で署名され、同じアクティビティの種類をサポートするアプリ間で継続できます。 Xamarin.iOS アプリでハンドオフを実装するには、ユーザー アクティビティ オブジェクト (UIKit
または AppKit
) を作成し、アクティビティを追跡するようにオブジェクトの状態を更新し、受信デバイスでアクティビティを続行する必要があります。
ユーザー アクティビティの識別
ハンドオフを実装する最初の手順は、アプリがサポートするユーザー アクティビティの種類を特定し、それらのアクティビティのうち、別のデバイスでの継続の候補となるものを確認することです。 たとえば、ToDo アプリでは、アイテムの編集を 1 つのユーザー アクティビティの種類としてサポートし、使用可能な項目リストを別のユーザー アクティビティの種類として参照できます。
アプリは、必要な数のユーザー アクティビティの種類を作成できます。1 つは、アプリが提供する任意の関数用です。 ユーザー アクティビティの種類ごとに、アプリは種類のアクティビティの開始と終了を追跡する必要があり、別のデバイスでそのタスクを続行するには、最新の状態情報を維持する必要があります。
ユーザー アクティビティは、同じチーム ID で署名された任意のアプリで続行でき、送受信アプリ間で 1 対 1 のマッピングを行う必要はありません。 たとえば、特定のアプリは、別のデバイス上の異なる個々のアプリによって消費される 4 種類のアクティビティを作成できます。 これは、アプリの Mac バージョン (多くの機能を持つ可能性がある) と iOS アプリの間で一般的に発生します。各アプリは小さく、特定のタスクに重点を置きます。
アクティビティの種類識別子の作成
アクティビティの種類識別子は、特定のユーザー アクティビティの種類を一意に識別するために使用される、アプリの Info.plist ファイルの NSUserActivityTypes
配列に追加される短い文字列です。 配列には、アプリがサポートするアクティビティごとに 1 つのエントリがあります。 Apple では、競合を回避するために、アクティビティの種類識別子に逆引き DNS スタイルの表記を使用することを勧めています。 例えば、特定のアプリ ベースのアクティビティには com.company-name.appname.activity
、複数のアプリにまたがって実行できるアクティビティには com.company-name.activity
を指定します。
アクティビティの種類識別子は、アクティビティの種類を識別するために NSUserActivity
インスタンスを作成するときに使用されます。 アクティビティが別のデバイスで継続されると、アクティビティの種類 (アプリのチーム ID と共に) によって、アクティビティを続行するために起動するアプリが決まります。
例として、MonkeyBrowser という サンプル アプリを作成します。 このアプリには 4 つのタブが表示され、それぞれ異なる URL が Web ブラウザー ビューで開かれます。 ユーザーは、アプリを実行している別の iOS デバイスで任意のタブを続行できます。
この動作をサポートするのに必要なアクティビティの種類識別子を作成するには、Info.plist ファイルを編集し、"ソース" ビューに切り替えます。 NSUserActivityTypes
キーを追加し、次の識別子を作成します:
例の 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
メソッドは、アクティビティに対する変更が保存され、ローカルで使用可能なデバイス間で同期される前に呼び出されます。 このメソッドを使用すると、NSUserActivity
インスタンスの UserInfo
プロパティを送信する直前に変更できます。
NSUserActivity インスタンスの作成
アプリが別のデバイスで続行する可能性を提供する各アクティビティは、NSUserActivity
インスタンスにカプセル化する必要があります。 アプリは必要な数のアクティビティを作成できます。これらのアクティビティの性質は、該当するアプリの機能と機能に依存します。 たとえば、メール アプリでは、新しいメッセージを作成するためのアクティビティと、メッセージを読み取るためのアクティビティを作成できます。
このサンプル アプリでは、ユーザーがタブ付き Web ブラウザー ビューのいずれかに新しい URL を入力するたびに、新しい NSUserActivity
が作成されます。 次のコードでは、特定のタブの状態を格納されます:
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 辞書の設定
上で説明したように、NSUserActivity
クラスの UserInfo
プロパティは、特定のアクティビティの状態を定義するために使用されるキーと値のペアの NSDictionary
です。 UserInfo
に格納される値は、NSArray
、NSData
、NSDate
、NSDictionary
、NSNull
、NSNumber
、NSSet
、NSString
、または NSURL
のいずれかの型である必要があります。 iCloud ドキュメントを指す NSURL
データ値は、受信デバイス上の同じドキュメントを指すよう自動的に調整されます。
上の例では、 NSMutableDictionary
オブジェクトを作成し、指定されたタブでユーザーが現在表示していた URL を指定する単一のキーを設定しました。ユーザー アクティビティの 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;
}
上の例では、各ビュー コントローラーは AppDelegate
に登録し、アクティビティ インジケーターと、アクティビティが現在のデバイスに渡されることをユーザーに知らせるメッセージを表示するパブリック PreparingToHandoff
メソッドを持っています。 例:
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...");
}
指定されたアクティビティを実際に続行するために、AppDelegate
の ContinueUserActivity
が呼び出されます。 ここでも、サンプル アプリから:
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);
}
ドキュメント ベースのアプリの場合、ContinueUserActivity
メソッドを実装しない場合、または false
を返した場合、UIKit
または AppKit
はアクティビティを自動的に再開できます。 詳細については、以下の「ドキュメントベースのアプリでのハンドオフのサポート」セクションを参照してください。
ハンドオフの適切な失敗
ハンドオフは、緩やかに接続された iOS デバイスと OS X デバイス間の情報の送信に依存するため、転送プロセスが失敗することがあります。 これらのエラーを適切に処理し、発生した状況をユーザーに通知するようにアプリを設計する必要があります。
エラーが発生した場合は、AppDelegate
の DidFailToContinueUserActivitiy
メソッドが呼び出されます。 次に例を示します。
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 (および継続する特定の項目を識別するために必要な構文) を認識している場合は、NSUserActivity
インスタンスの WebpageURL
プロパティでこの情報をエンコードできます。 受信側のデバイスに、継続を処理するための適切なネイティブ アプリがインストールされていない場合は、指定された Web インターフェイスを呼び出すことができます。
Web ブラウザーからネイティブ アプリへのハンドオフ
ユーザーが送信元デバイスで Web ベースのインターフェイスを使用していて、受信側デバイスのネイティブ アプリが WebpageURL
プロパティのドメイン部分を要求している場合、システムはそのアプリを継続のハンドルとして使用します。 新しいデバイスは、アクティビティの種類を BrowsingWeb
としてマークする NSUserActivity
インスタンスを受け取り、WebpageURL
にはユーザーがアクセスした URL が含まれ、UserInfo
ディクショナリは空になります。
アプリがこの種類のハンドオフに参加するには、形式が <service>:<fully qualified domain name>
(例: activity continuation:company.com
) の com.apple.developer.associated-domains
エンタイトルメントでドメインを要求する必要があります。
指定したドメインが WebpageURL
プロパティの値と一致する場合、ハンドオフは、そのドメインの Web サイトから承認済みアプリ ID の一覧をダウンロードします。 Web サイトでは、apple-app-site-association (たとえば、https://company.com/apple-app-site-association
) という名前の署名済み JSON ファイルに承認済み ID の一覧を提供する必要があります。
この JSON ファイルには、<team identifier>.<bundle identifier>
形式のアプリ ID の一覧を指定する辞書が含まれています。 次に例を示します。
{
"activitycontinuation": {
"apps": [ "YWBN8XTPBJ.com.company.FirstApp",
"YWBN8XTPBJ.com.company.SecondApp" ]
}
}
JSON ファイルに署名するには (application/pkcs7-mime
の正しい Content-Type
が含まれるように)、ターミナル アプリと、iOS によって信頼されている証明機関によって発行された証明書とキーを使用して openssl
コマンドを使用します (一覧については、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
コマンドは、Web サイトに配置する署名付き JSON ファイルを、apple-app-site-association URL に出力します。 次に例を示します。
https://example.com/apple-app-site-association.
アプリは、WebpageURL
ドメインがその com.apple.developer.associated-domains
エンタイトルメントにあるすべてのアクティビティを受け取ります。 http
プロトコルと https
プロトコルのみがサポートされており、他のプロトコルでは例外が発生します。
ドキュメント ベースのアプリでのハンドオフのサポート
前述のように、iOS および OS X では、アプリの info.plist ファイルに NSUbiquitousDocumentUserActivityType
の CFBundleDocumentTypes
キーが含まれている場合、ドキュメント ベースのアプリは iCloud ベースのドキュメントのハンドオフを自動的にサポートします。 次に例を示します。
<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
に設定し、UpdateUserActivityState
メソッドで UserInfo
辞書を更新する必要があります。
UserActivity
プロパティは、任意のスレッドから使用でき、キー値監視 (KVO) プロトコルに準拠しているため、iCloud との間でドキュメントの移動時にドキュメントを同期するために使用できます。 ドキュメントを閉じると、UserActivity
プロパティは無効になります。
詳細については、ドキュメント ベースのアプリの Apple のユーザー アクティビティ サポート ドキュメントを参照してください。
レスポンダーでのハンドオフのサポート
レスポンダー (iOS 上の UIResponder
または OS X の NSResponder
から継承) をアクティビティに関連付けるには UserActivity
プロパティを設定します。 システムは、UserActivity
プロパティを適切な時刻に自動的に保存し、レスポンダーの UpdateUserActivityState
メソッドを呼び出して、AddUserInfoEntriesFromDictionary
メソッドを使用して現在のデータをユーザー アクティビティ オブジェクトに追加します。
継続ストリームのサポート
アクティビティを続行するために必要な情報の量を、最初のハンドオフ ペイロードによって効率的に転送できない場合があります。 このような状況では、受信側のアプリは、それ自体と元のアプリの間に 1 つ以上のストリームを確立してデータを転送できます。
元のアプリは、NSUserActivity
インスタンスの SupportsContinuationStreams
プロパティを true
に設定します。 次に例を示します。
// 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 ();
受信アプリは、その AppDelegate
内の NSUserActivity
の 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
メソッドを呼び出してストリームを受信し、再開デバイスでユーザー アクティビティを続行するように要求されたデータを提供します。
NSInputStream
を使用してストリーム データへの読み取り専用アクセスを提供し、NSOutputStream
は書き込み専用アクセスを提供します。 ストリームは要求と応答の方法で使用する必要があります。この方法では、受信アプリはより多くのデータを要求し、元のアプリが提供します。 そのため、送信元デバイスの出力ストリームに書き込まれたデータは、継続デバイス上の入力ストリームから読み取られます。その逆も同様です。
継続ストリームが必要な場合でも、2 つのアプリ間の通信は最小限に抑える必要があります。
詳細については、Apple の継続ストリームの使用に関するドキュメントを参照してください。
ハンドオフのベスト プラクティス
ハンドオフによるユーザー アクティビティのシームレスな継続の正常な実装には、関連するすべてのコンポーネントが含まれているため、慎重な設計が必要です。 Apple では、ハンドオフ対応アプリに次のベスト プラクティスを採用することをお勧めします:
- アクティビティの状態を引き続き関連付けるために、可能な限り最小のペイロードを要求するようにユーザー アクティビティを設計します。 ペイロードが大きいほど、継続の開始にかかる時間が長くなります。
- 継続を成功させるために大量のデータを転送する必要がある場合は、構成とネットワークのオーバーヘッドに関連するコストを考慮してください。
- 大規模な Mac アプリでは、iOS デバイス上の複数の小規模なタスク固有のアプリによって処理されるユーザー アクティビティを作成するのが一般的です。 さまざまなアプリと OS のバージョンは、正常に連携するか、適切に失敗するように設計する必要があります。
- アクティビティの種類を指定するときは、逆引き DNS 表記を使用して競合を回避します。 アクティビティが特定のアプリに固有の場合は、その名前を型定義に含める必要があります (たとえば
com.myCompany.myEditor.editing
)。 アクティビティが複数のアプリで機能する場合は、定義からアプリ名を削除します (たとえばcom.myCompany.editing
)。 - アプリでユーザー アクティビティ (
NSUserActivity
) の状態を更新する必要がある場合は、NeedsSave
プロパティをtrue
に設定します。 必要に応じて、Handoff はデリゲートのUserActivityWillSave
メソッドを呼び出して、必要に応じてUserInfo
辞書を更新できるようにします。 - ハンドオフ プロセスは受信側デバイスですぐに初期化されない可能性があるため、
AppDelegate
のWillContinueUserActivity
を実装し、継続が開始されることをユーザーに通知する必要があります。
ハンドオフ アプリの例
Xamarin.iOS アプリでハンドオフを使用する例として、MonkeyBrowser サンプル アプリがあります。 アプリには、ユーザーが Web を閲覧するために使用できる 4 つのタブがあり、それぞれに特定のアクティビティの種類 (天気、お気に入り、コーヒー ブレーク、仕事) があります。
任意のタブで、ユーザーが新しい URL を入力し、[移動] ボタンをタップすると、ユーザーが現在参照している URL を含む新しい NSUserActivity
がタブに作成されます:
別のユーザーのデバイスに MonkeyBrowser アプリがインストールされていて、同じユーザー アカウントを使用して iCloud にサインインしている場合、同じネットワーク上にあり、上記のデバイスに近接している場合は、ホーム画面 (左下隅) にハンドオフ アクティビティが表示されます:
ユーザーがハンドオフ アイコン上にドラッグすると、アプリが起動し、NSUserActivity
で指定されたユーザー アクティビティが新しいデバイスで続行されます:
ユーザー アクティビティが別の Apple デバイスに正常に送信されると、送信側デバイスの NSUserActivity
は、その NSUserActivityDelegate
で UserActivityWasContinued
メソッドの呼び出しを受け取り、ユーザー アクティビティが別のデバイスに正常に転送されたことを通知します。
まとめ
この記事では、ユーザーの複数の Apple デバイス間でユーザー アクティビティを続行するために使用されるハンドオフ フレームワークの概要について説明しました。 次に、Xamarin.iOS アプリでハンドオフを有効にして実装する方法について説明しました。 最後に、利用可能なさまざまな種類のハンドオフ継続とハンドオフのベスト プラクティスについて説明しました。