Xamarin.iOS 中的交接
本文介绍如何在 Xamarin.iOS 应用中使用 Handoff,以在用户的其他设备上运行的应用之间传输用户活动。
Apple 在 iOS 8 和 OS X Yosemite (10.10) 中引入了接力功能,为用户提供一种通用机制,可将某个设备上启动的活动传输到运行同一应用的另一台设备或支持相同活动的其他应用。
本文将快速了解如何在 Xamarin.iOS 应用中启用活动共享,并详细介绍 Handoff 框架:
关于讲义
Apple 在 iOS 8 和 OS X Yosemite (10.10) 中引入了接力(也称为连续互通),它可让用户在其一台设备(iOS 或 Mac)上启动活动,并在其另一台设备(由用户的 iCloud 帐户标识)上继续执行相同的活动。
iOS 9 中对接力进行了扩展,还支持新增强的搜索功能。 有关详细信息,请参阅我们的搜索增强文档。
例如,用户可以在其 i 上启动电子邮件电话并无缝地继续其 Mac 上的电子邮件,并填写了所有相同的邮件信息,并且光标位于他们在 iOS 中留下的同一位置。
共享同一 团队 ID 的任何应用都有资格使用 Handoff 继续跨应用的用户活动,前提是这些应用是通过 iTunes App Store 交付的,或者由注册的开发人员(适用于 Mac、企业或即席应用)签名。
任何 NSDocument
或 UIDocument
基于的应用都自动内置了 Handoff 支持,并且需要最少的更改来支持 Handoff。
继续用户活动
该 NSUserActivity
类(以及对某些小更改 UIKit
和 AppKit
)提供对定义用户活动的支持,这些活动可能在另一个用户的设备上继续。
若要将活动传递给另一个用户设备,该活动必须封装在标记为“当前活动”的实例NSUserActivity
中,使其有效负载集(用于执行延续的数据),然后必须将活动传输到该设备。
传递传递最少的信息以定义要继续的活动,并通过 iCloud 同步更大的数据包。
在接收设备上,用户将收到一条通知,指出活动可用于延续。 如果用户选择在新设备上继续活动,则会启动指定的应用(如果尚未运行),并使用有效 NSUserActivity
负载重新启动活动。
只有共享同一开发人员团队 ID 并响应给定 活动类型的 应用才有资格继续。 应用定义它在 Info.plist 文件的键下NSUserActivityTypes
支持的活动类型。 鉴于此情况,继续设备会根据团队 ID、活动类型和活动标题(可选)选择应用来执行延续。
接收应用使用字典UserInfo
中的信息NSUserActivity
来配置其用户界面并还原给定活动的状态,使转换看起来与最终用户无缝。
如果延续需要的信息比可以通过 a NSUserActivity
高效发送的信息,则恢复应用可以向发起的应用发送调用,并建立一个或多个流来传输所需数据。 例如,如果活动正在编辑包含多个图像的大型文本文档,则需要流式传输传输接收设备上继续活动所需的信息。 有关详细信息,请参阅 下面的“支持延续流 ”部分。
如上所述, NSDocument
或 UIDocument
基于应用自动内置了 Handoff 支持。 有关详细信息,请参阅 下面的“基于文档的应用 ”部分中的支持切换。
NSUserActivity 类
该 NSUserActivity
类是 Handoff 交换中的主要对象,用于封装可用于延续的用户活动的状态。 应用将实例化它支持的任何活动的副本 NSUserActivity
,并希望在另一台设备上继续。 例如,文档编辑器将为当前打开的每个文档创建一个活动。 但是,只有最前面的文档(显示在最前面窗口或 Tab 中)是 当前活动 ,并有可用的延续。
NSUserActivity
实例由其ActivityType
属性Title
标识。 UserInfo
字典属性用于传递有关活动状态的信息。 NeedsSave
如果要通过NSUserActivity
'委托延迟加载状态信息,请将属性设置为true
。 AddUserInfoEntries
使用该方法将其他客户端的新数据合并到字典中UserInfo
,以保留活动的状态。
NSUserActivityDelegate 类
用于NSUserActivityDelegate
使信息保持NSUserActivity
UserInfo
“字典最新”,并与活动的当前状态同步。 当系统需要更新活动中的信息(例如在另一台设备上继续之前),它会调用 UserActivityWillSave
委托的方法。
你需要实现UserActivityWillSave
该方法,并对(如UserInfo
Title
,等)进行任何更改NSUserActivity
,以确保它仍然反映当前活动的状态。 当系统调用 UserActivityWillSave
该方法时, NeedsSave
将清除标志。 如果修改活动的任何数据属性,则需要再次设置为NeedsSave
true
。
可以选择性地自动拥有UIKit
或AppKit
管理用户活动,而不是使用UserActivityWillSave
上述方法。 为此,请设置响应方对象的 UserActivity
属性并实现 UpdateUserActivityState
该方法。 有关详细信息,请参阅下面的响应者中的支持切换部分。
应用框架支持
UIKit
(iOS) 和 AppKit
(OS X) 在响应方 (NSResponder
UIResponder
/) 和AppDelegate
类中NSDocument
为 Handoff 提供内置支持。 虽然每个 OS 都实现 Handoff 略有不同,但基本机制和 API 是相同的。
基于文档的应用中的用户活动
基于文档的 iOS 和 OS X 应用会自动内置支持 Handoff。 若要激活此支持,需要为应用 Info.plist 文件中的每个CFBundleDocumentTypes
条目添加一个NSUbiquitousDocumentUserActivityType
键和值。
如果存在此键,则UIDocument
同时NSDocument
自动为指定的类型的基于 iCloud 的文档创建NSUserActivity
实例。 你需要为应用支持的每种文档类型提供一个活动类型,并且多个文档类型可以使用相同的活动类型。 同时NSDocument
自动UIDocument
填充UserInfo
其FileURL
属性值NSUserActivity
的属性。
在 OS X 上,当文档的窗口成为主窗口时, NSUserActivity
由 AppKit
响应方和与之关联的管理者会自动成为当前活动。 在 iOS 上,对于 NSUserActivity
受 UIKit
管理的对象,你必须显式调用 BecomeCurrent
方法,或在应用来到前台时设置文档 UserActivity
的属性 UIViewController
。
AppKit
将在 OS X 上自动还原以这种方式创建的任何 UserActivity
属性。如果 ContinueUserActivity
方法返回 false
或未实现,则会发生此情况。 在这种情况下,将使用该方法NSDocumentController
打开OpenDocument
文档,然后它将收到方法RestoreUserActivityState
调用。
有关详细信息,请参阅下面的“基于文档的应用”部分中的支持切换。
用户活动和响应者
UIKit
如果将用户活动设置为响应方对象的UserActivity
属性,则可以AppKit
同时自动管理该活动。 如果状态已修改,则需要将响应方UserActivity
的属性设置为 。NeedsSave
true
系统在通过调用其UpdateUserActivityState
方法来更新状态后,系统会自动保存UserActivity
所需的时间。
如果多个响应者共享单个 NSUserActivity
实例,则当系统更新用户活动对象时,它们会收到回调 UpdateUserActivityState
。 响应方需要调用 AddUserInfoEntries
该方法来更新 NSUserActivity
'字典 UserInfo
',以反映当前活动状态。 每次 UserInfo
调用前 UpdateUserActivityState
都会清除字典。
若要将自身与活动取消关联,响应者可以将其 UserActivity
属性设置为 null
。 当应用框架托管 NSUserActivity
实例没有更多关联的响应者或文档时,它将自动失效。
有关详细信息,请参阅下面的响应者中的支持切换部分。
用户活动和 AppDelegate
处理 Handoff 延续时,应用 AppDelegate
是它的主要入口点。 当用户响应 Handoff 通知时,将启动相应的应用(如果尚未运行), WillContinueUserActivityWithType
并调用该方法 AppDelegate
。 此时,应用应通知用户继续开始。
调用 's ContinueUserActivity
方法时AppDelegate
,将NSUserActivity
传递该实例。 此时,应配置应用的用户界面并继续给定活动。
在 Xamarin 应用中启用切换
由于 Handoff 施加的安全要求,必须在 Apple 开发人员门户和 Xamarin.iOS 项目文件中正确配置使用 Handoff 框架的 Xamarin.iOS 应用。
请执行以下操作:
登录到 Apple 开发人员门户。
单击 “证书”、“标识符和配置文件”。
如果尚未执行此操作,请单击“ 标识符 ”并为应用创建 ID(例如
com.company.appname
),否则请编辑现有 ID。确保已为给定 ID 检查 iCloud 服务:
保存所做更改。
单击“预配配置文件>开发”并为应用创建新的开发预配配置文件:
下载并安装新的预配配置文件,或使用 Xcode 下载并安装配置文件。
编辑 Xamarin.iOS 项目选项,并确保使用刚刚创建的预配配置文件:
接下来,编辑 Info.plist 文件,并确保使用用于创建预配配置文件的应用 ID:
滚动到“后台模式”部分,并检查以下项:
保存对所有文件的更改。
有了这些设置,应用程序即可访问 Handoff Framework API。 有关预配的详细信息,请参阅我们的 设备预配 和 预配应用 指南。
实现 Handoff
可以在使用同一开发人员团队 ID 进行签名的应用中继续用户活动,并支持相同的活动类型。 在 Xamarin.iOS 应用中实现 Handoff 需要创建用户活动对象(位于或AppKit
),UIKit
更新对象的状态以跟踪活动,并继续接收设备上的活动。
标识用户活动
实现 Handoff 的第一步是确定应用支持的用户活动类型,并查看哪些活动是其他设备上继续的良好候选项。 例如:ToDo 应用可能支持将项目编辑为一个用户活动类型,并支持以另一种形式浏览可用项列表。
应用可以根据需要创建任意数量的用户活动类型,一个用于应用提供的任何函数。 对于每个用户活动类型,应用需要跟踪类型开始和结束的时间,并且需要保持最新的状态信息才能在另一台设备上继续该任务。
可以在使用同一团队 ID 签名的任何应用上继续执行用户活动,而无需在发送和接收应用之间进行任何一对一映射。 例如,给定的应用可以创建四种不同类型的活动,这些活动由其他设备上的不同单个应用使用。 这是 Mac 版应用(可能具有许多功能和功能)和 iOS 应用(其中每个应用较小且侧重于特定任务)之间的常见情况。
创建活动类型标识符
活动类型标识符是添加到NSUserActivityTypes
应用 Info.plist 文件的数组中的短字符串,用于唯一标识给定的用户活动类型。 对于应用支持的每个活动,数组中将有一个条目。 Apple 建议对活动类型标识符使用反向 DNS 样式表示法以避免冲突。 例如: com.company-name.appname.activity
针对特定基于应用的活动或 com.company-name.activity
可跨多个应用运行的活动。
创建 NSUserActivity
实例以标识活动类型时,将使用活动类型标识符。 在另一台设备上继续活动时,活动类型(以及应用的团队 ID)确定要启动以继续活动的应用。
例如,我们将创建一个名为 MonkeyBrowser 的示例应用(请在此处下载)。 此应用将显示四个选项卡,每个选项卡在 Web 浏览器视图中打开不同的 URL。 用户将能够继续运行应用的其他 iOS 设备上的任意选项卡。
若要创建所需的活动类型标识符以支持此行为,请编辑 Info.plist 文件并切换到 源 视图。 NSUserActivityTypes
添加密钥并创建以下标识符:
我们创建了四个新的活动类型标识符,其中一个用于示例 MonkeyBrowser 应用中的每个选项卡。 创建自己的应用时,请将数组的内容 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
。 应用可以根据需要创建任意数量的活动,这些活动的性质取决于相关应用的功能和功能。 例如,电子邮件应用可能会创建一个用于创建新邮件的活动,另一个活动用于阅读邮件。
对于示例应用, 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 字典
如上所述, UserInfo
类的属性 NSUserActivity
是用于 NSDictionary
定义给定活动的状态的键值对。 存储在其中的UserInfo
值必须是下列类型之一:NSArray
、、、NSData
、、NSNull
NSDictionary
NSDate
、NSNumber
、、 NSSet
或。 NSString
NSURL
NSURL
将自动调整指向 iCloud 文档的数据值,以便它们指向接收设备上的相同文档。
在上面的示例中,我们创建了一个 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 建议将发送的信息保持在最低水平,以确保活动及时发送到接收设备。 如果需要更大的信息,如需要编辑附加到文档的图像,则应使用延续流。 有关更多详细信息,请参阅下面的“支持延续流”部分。
继续活动
交接会自动通知本地 iOS 和 OS X 设备,这些设备与原始设备物理邻近,并登录到同一 iCloud 帐户,了解可连续用户活动的可用性。 如果用户选择在新设备上继续活动,系统将启动相应的应用(基于团队 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...");
}
ContinueUserActivity
将调用 AppDelegate
以实际继续给定的活动。 同样,在我们的示例应用中:
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
可以自动恢复活动。 有关详细信息,请参阅下面的“基于文档的应用”部分中的支持切换。
失败交接优雅
由于 Handoff 依赖于集合之间松散连接的 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(以及用于标识给定项的必需语法),则可以在实例的属性NSUserActivity
中WebpageURL
对此信息进行编码。 如果接收设备未安装适当的本机应用来处理延续,则可以调用提供的 Web 界面。
Web 浏览器到本机应用交接
如果用户在发起设备上使用基于 Web 的界面,并且接收设备上的本机应用声明属性的 WebpageURL
域部分,则系统将使用该应用处理延续。 新设备将收到一个 NSUserActivity
实例,该实例将标记为活动类型 BrowsingWeb
,并且 WebpageURL
将包含用户正在访问的 URL, UserInfo
字典将为空。
要使应用参与这种类型的 Handoff,它必须以具有格式<service>:<fully qualified domain name>
的权利声明域com.apple.developer.associated-domains
(例如: activity continuation:company.com
) 。
如果指定的域与属性的值匹配 WebpageURL
,Handoff 将从该域的网站下载已批准的应用 ID 列表。 该网站必须在名为 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-Type
application/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
命令将输出一个已签名的 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 文件包含密钥CFBundleDocumentTypes
NSUbiquitousDocumentUserActivityType
,基于文档的应用将自动支持基于 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
的属性提供)可由应用中的其他对象引用,并用于还原延续状态。 例如,跟踪项目选择和文档位置。 每当状态更改并更新方法中的UpdateUserActivityState
字典时,都需要将此活动NeedsSave
属性设置为true
。UserInfo
该 UserActivity
属性可从任何线程使用,并符合键值观察(KVO)协议,因此可用于在文档移入和移出 iCloud 时保持同步。 关闭文档时,该 UserActivity
属性将失效。
有关详细信息,请参阅 Apple 在基于文档的应用文档中的用户活动支持。
支持响应者中的切换
可以通过设置UserActivity
响应者的属性(从 UIResponder
iOS 或 NSResponder
OS X 上的响应者继承)关联到活动。 系统在适当的时间自动保存 UserActivity
属性,调用响应程序 UpdateUserActivityState
的方法,以使用 AddUserInfoEntriesFromDictionary
该方法将当前数据添加到用户活动对象。
支持延续流
在某些情况下,初始传递有效负载无法有效地传输继续活动所需的信息量。 在这些情况下,接收应用可以在自身与原始应用之间建立一个或多个流来传输数据。
原始应用将实例的属性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 ();
然后,接收应用可以调用GetContinuationStreams
其AppDelegate
中的方法NSUserActivity
来建立流。 例如:
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
仅写访问权限。 流应以请求和响应方式使用,其中接收应用请求更多数据和发起应用提供这些数据。 因此,从继续设备上的输入流中读取写入到源设备上的输出流的数据,反之亦然。
即使在需要延续流的情况下,两个应用之间的来回通信也应最少。
有关详细信息,请参阅 Apple 的 “使用延续流” 文档。
交接最佳做法
通过 Handoff 成功实现用户活动的无缝延续需要经过精心设计,因为涉及的所有组件。 Apple 建议对已启用 Handoff 的应用采用以下最佳做法:
- 将用户活动设计为需要尽可能小的有效负载来关联要继续的活动状态。 有效负载越大,开始延续所需的时间就越长。
- 如果必须传输大量数据才能成功延续,请考虑到配置和网络开销所涉及的成本。
- 大型 Mac 应用通常会创建由 iOS 设备上的多个、更小的任务特定应用处理的用户活动。 不同的应用和 OS 版本应设计为很好地协同工作或正常失败。
- 指定活动类型时,请使用反向 DNS 表示法以避免冲突。 如果某个活动特定于给定应用,则其名称应包含在类型定义中(例如
com.myCompany.myEditor.editing
)。 如果活动可以跨多个应用工作,请从定义中删除应用名称(例如com.myCompany.editing
)。 - 如果你的应用需要更新用户活动(
NSUserActivity
)的状态,请将NeedsSave
属性设置为true
。 在适当的时间,Handoff 将调用委托UserActivityWillSave
的方法,以便你可以根据需要更新UserInfo
字典。 - 由于接接过程可能不会在接收设备上立即初始化,因此应实现
AppDelegate
'sWillContinueUserActivity
,并通知用户继续即将启动。
示例 Handoff 应用
作为在 Xamarin.iOS 应用中使用 Handoff 的示例,本指南中包含了 MonkeyBrowser 示例应用。 该应用有四个选项卡,用户可用于浏览 Web,每个选项卡都有给定的活动类型:天气、收藏夹、咖啡休息和工作。
在任何选项卡上,当用户输入新 URL 并点击 “转到 ”按钮时,将为该选项卡创建一个新 NSUserActivity
URL,其中包含用户当前正在浏览的 URL:
如果另一个用户设备 安装了 MonkeyBrowser 应用,则使用同一用户帐户登录到 iCloud,位于同一网络上,并且靠近上述设备,则“切换活动”将显示在主屏幕上(左下角):
如果用户在 Handoff 图标上向上拖动,应用将启动,并在 NSUserActivity
新设备上继续指定用户活动:
成功将用户活动发送到另一个 Apple 设备后,发送设备将收到对其UserActivityWasContinued
方法NSUserActivityDelegate
的NSUserActivity
调用,以告知用户活动已成功传输到另一台设备。
总结
本文介绍了用于在多个用户的 Apple 设备之间继续执行用户活动的 Handoff 框架。 接下来,它演示了如何在 Xamarin.iOS 应用中启用和实现 Handoff。 最后,它讨论了可用的不同类型的 Handoff 延续和 Handoff 最佳做法。