Xamarin.iOS 中的 Siri 快捷方式

iOS 10 中,Apple 推出了 SiriKit,从而可以构建与 Siri 交互的消息、VoIP 呼叫、付款、锻炼、骑行预订和照片搜索应用。

iOS 11 中,SiriKit 支持更多类型的应用,并为 UI 自定义提供了更大灵活性。

iOS 12 添加了 Siri 快捷方式,允许所有类型的应用向 Siri 公开其功能。 Siri 了解某些基于应用的任务何时与用户最相关,并利用这些知识通过快捷方式建议潜在操作。 点击快捷方式或使用语音命令调用该快捷方式将打开应用或运行后台任务。

快捷方式应该用于加速用户完成常见任务的能力,在许多情况下甚至无需打开相关应用。

示例应用:Soup Chef

若要更好地了解 Siri 快捷方式,请查看 Soup Chef 示例应用。 Soup Chef 允许用户从虚构的汤餐厅下订单,查看他们的订单历史记录,并通过与 Siri 交互来定义在订购汤时要使用的短语。

提示

在 iOS 12 模拟器或设备上测试 Soup Chef 之前,请启用以下两个设置,这两个设置在调试快捷方式时很有用:

  • 在“设置”应用中,启用“开发人员”>“显示最近使用的快捷方式”。
  • 在“设置”应用中,启用“开发人员”>“在锁屏界面上显示捐款”。

通过这些调试设置,可以轻松地在 iOS 锁屏界面和搜索屏幕上查找最近创建的(而不是预测)快捷方式。

若要使用示例应用,请执行以下操作:

  • 在 iOS 12 模拟器或设备上安装并运行 Soup Chef 示例应用。
  • 单击右上角的 + 按钮创建新订单。
  • 选择一种类型的汤,指定数量和选项,然后点击“下订单”。
  • 在“订单历史记录”屏幕上,点击新创建的订单以查看其详细信息。
  • 在订单详细信息屏幕底部,点击“添加到 Siri”。
  • 录制与订单关联的语音短语,然后点击“完成”。
  • 最小化 Soup Chef,调用 Siri,并使用录制的语音短语再次下订单。
  • Siri 完成订单后,重新打开 Soup Chef,并注意到新订单已在“订单历史记录”屏幕上列出。

示例应用演示如何执行以下操作:

Info.plist 和 Entitlements.plist

在更深入地挖掘 Soup Chef 代码之前,请查看其 Info.plistEntitlements.plist 文件。

Info.plist

SoupChef 项目中的 Info.plist 文件将捆绑标识符定义为 com.xamarin.SoupChef。 此捆绑标识符将用作本文档后面讨论的意图和意图 UI 扩展的捆绑标识符的前缀。

Info.plist 文件还包含以下条目:

<key>NSUserActivityTypes</key>
<array>
    <string>OrderSoupIntent</string>
    <string>com.xamarin.SoupChef.viewMenu</string>
</array>

NSUserActivityTypes 键/值对指示 Soup Chef 知道如何处理 OrderSoupIntent 以及 ActivityType 为“com.xamarin.SoupChef.viewMenu”的 NSUserActivity

传递给应用本身的活动和自定义意图(而不是其扩展)在 AppDelegate (UIApplicationDelegate) 中由 ContinueUserActivity 方法处理。

Entitlements.plist

SoupChef 项目中的 Entitlements.plist 文件包含以下条目:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.xamarin.SoupChef</string>
</array>
<key>com.apple.developer.siri</key>
<true/>

此配置指示应用使用“group.com.xamarin.SoupChef”应用组。 SoupChefIntents 应用扩展使用相同的应用组,这允许两个项目共享 NSUserDefaults 数据。

com.apple.developer.siri 键指示应用与 Siri 进行交互。

注意

SoupChef 项目的生成配置将“自定义权利”设置为 Entitlements.plist

使用 NSUserActivity 快捷方式打开应用

若要创建打开应用以显示特定内容的快捷方式,请创建 NSUserActivity 并将其附加到要打开快捷方式的屏幕的视图控制器。

设置 NSUserActivity

在菜单屏幕上,SoupMenuViewController 会创建 NSUserActivity 并将其分配给视图控制器的 UserActivity 属性:

public override void ViewDidLoad()
{
    base.ViewDidLoad();
    UserActivity = NSUserActivityHelper.ViewMenuActivity;
}

设置 UserActivity 属性会将活动捐赠给 Siri。 通过这次捐赠,Siri 获得了有关此活动何时何地与用户相关的信息,并学会了在未来更好地提出建议。

NSUserActivityHelperSoupChef 解决方案中 SoupKit 类库中包含的一个实用程序类。 它会创建 NSUserActivity,并设置与 Siri 和搜索相关的各种属性:

public static string ViewMenuActivityType = "com.xamarin.SoupChef.viewMenu";

public static NSUserActivity ViewMenuActivity {
    get
    {
        var userActivity = new NSUserActivity(ViewMenuActivityType)
        {
            Title = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_TITLE", "View menu activity title"),
            EligibleForSearch = true,
            EligibleForPrediction = true
        };

        var attributes = new CSSearchableItemAttributeSet(NSUserActivityHelper.SearchableItemContentType)
        {
            ThumbnailData = UIImage.FromBundle("tomato").AsPNG(),
            Keywords = ViewMenuSearchableKeywords,
            DisplayName = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_TITLE", "View menu activity title"),
            ContentDescription = NSBundleHelper.SoupKitBundle.GetLocalizedString("VIEW_MENU_CONTENT_DESCRIPTION", "View menu content description")
        };
        userActivity.ContentAttributeSet = attributes;

        var phrase = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_SUGGESTED_PHRASE", "Voice shortcut suggested phrase");
        userActivity.SuggestedInvocationPhrase = phrase;
        return userActivity;
    }
}

具体而言,请注意以下功能:

处理 NSUserActivity 快捷方式

若要处理用户调用的 NSUserActivity 快捷方式,iOS 应用程序必须替代 AppDelegate 类的 ContinueUserActivity 方法,并根据传入的 NSUserActivity 对象的 ActivityType 字段进行响应:

public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    // ...
    else if (userActivity.ActivityType == NSUserActivityHelper.ViewMenuActivityType)
    {
        HandleUserActivity();
        return true;
    }
    // ...
}

此方法调用 HandleUserActivity,后者会查找菜单屏幕的转场并调用它:

void HandleUserActivity()
{
    var rootViewController = Window?.RootViewController as UINavigationController;
    var orderHistoryViewController = rootViewController?.ViewControllers?.FirstOrDefault() as OrderHistoryTableViewController;
    if (orderHistoryViewController is null)
    {
        Console.WriteLine("Failed to access OrderHistoryTableViewController.");
        return;
    }
    var segue = OrderHistoryTableViewController.SegueIdentifiers.SoupMenu;
    orderHistoryViewController.PerformSegue(segue, null);
}

为 NSUserActivity 分配短语

若要为 NSUserActivity 分配短语,请打开 iOS“设置”应用,然后选择“Siri 和搜索”>“我的快捷方式”。 然后,选择快捷方式(在本例中为“订购午餐”)并录制短语。

调用 Siri 并使用此短语将打开 Soup Chef 到菜单屏幕。

使用自定义意图快捷方式执行任务

定义自定义意图

若要提供快捷方式来允许用户快速完成与应用相关的特定任务,请创建自定义意图。 自定义意图表示用户可能需要完成的任务、与该任务相关的参数以及任务执行产生的潜在响应。 根据自定义意图的定义方式,调用该意图可以打开应用或运行后台任务。

使用 Xcode 10 创建自定义意图。 在 SoupChef 存储库中,自定义意图在 OrderSoupIntentCodeGen(Objective-C 项目)中定义。 打开此项目,然后在 Project Navigator 中选择 Intents.intentdefinition 文件以查看 OrderSoup 意图。

请注意以下功能:

  • 该意图具有类别订单”。 有各种预定义的类别可用于自定义意图;选择最符合自定义意图将启用的任务的类别。 由于此解决方案是汤排序应用,因此 OrderSoupIntent 使用“订单”。
  • 确认”复选框指示 Siri 在执行任务之前是否必须请求确认。 对于 Soup Chef 中的“订购汤”意图,此选项已启用,因为用户正在购买。
  • .intentdefinition 文件的 Parameters 部分定义与快捷方式相关的参数。 若要下汤订单,Soup Chef 必须知道汤的类型、其数量以及任何关联的选项。 每个参数都有一个类型;不能由预定义类型表示的参数设置为“自定义”。
  • 快捷方式类型”界面描述了 Siri 在建议快捷方式时可以使用的各种参数组合。 关联的“标题”和“副标题”部分允许定义在向用户显示建议的快捷方式时 Siri 将使用的消息。
  • 对于任何可以执行的快捷方式,应选中“支持后台执行”复选框,而无需打开应用进行进一步用户交互。

定义自定义意图响应

嵌套在 OrderSoup 意图下方的 Response 项表示汤订单产生的潜在响应。

OrderSoup 意图的响应定义中,请注意以下功能:

  • 响应的属性可用于自定义向用户传达的消息。 OrderSoup 意图响应具有 soupwaitTime 属性。
  • 响应模板指定各种成功和失败消息,这些消息可用于指示意图的任务完成后的状态。
  • 对于指示成功的响应,应选中“成功”复选框。
  • OrderSoupIntent 成功响应使用 soupwaitTime 属性来提供一条友好且有用的信息,描述汤订单何时准备就绪。

为自定义意图生成代码

生成包含此自定义意图定义的 Xcode 项目会导致 Xcode 生成可用于以编程方式与自定义意图及其响应交互的代码。

若要查看此生成的代码,请执行以下操作:

  • 打开 AppDelegate.m
  • 将导入添加到自定义意图的头文件:#import "OrderSoupIntent.h"
  • 在类中的任何方法中,添加对 OrderSoupIntent 的引用。
  • 右键单击 OrderSoupIntent 并选择“跳转到定义”。
  • 右键单击新打开的文件 OrderSoupIntent.h,然后选择“在查找器中显示”。
  • 此操作将打开一个查找器窗口,其中包含内含生成的代码的 .h.m 文件。

此生成的代码包括:

  • OrderSoupIntent – 表示自定义意图的类。
  • OrderSoupIntentHandling – 定义将用于确认应执行意图的方法以及实际执行意图的方法的协议。
  • OrderSoupIntentResponseCode – 定义各种响应状态的枚举。
  • OrderSoupIntentResponse – 表示对意图执行的响应的类。

创建到自定义意图的绑定

若要在 Xamarin.iOS 应用中使用 Xcode 生成的代码,请为其创建 C# 绑定。

创建静态库和 C# 绑定定义

SoupChef 存储库中,查看 OrderSoupIntentStaticLib 文件夹,并打开 OrderSoupIntentStaticLib.xcodeproj Xcode 项目。

Cocoa Touch 静态库项目包含 Xcode 生成的 OrderSoupIntent.hOrderSoupIntent.m 文件。

配置静态库项目生成设置

在 Xcode 项目导航器中,选择顶级项目 OrderSoupIntentStaticLib,然后导航到“生成阶段”>“编译源”。 请注意,此处列出了 OrderSoupIntent.m(导入 OrderSoupIntent.h)。 在“将二进制文件链接到库”中,请注意,Intents.frameworkFoundation.framework 包括在内。 有了这些设置,框架就会正确生成。

生成静态库并生成 C# 绑定定义

若要生成静态库并为其生成 C# 绑定定义,请执行以下步骤:

  • 安装 Objective Sharpie,该工具用于从 Xcode 创建的 .h.m 文件生成绑定定义。

  • 将系统配置为使用 Xcode 10 命令行工具

    警告

    更新所选命令行工具会影响系统上所有已安装的 Xcode 版本。 使用完 Soup Chef 示例应用后,请确保将此设置恢复到其原始配置。

    • 在 Xcode 中,选择“Xcode”>“首选项”>“位置”,然后将“命令行工具”设置为系统上可用的最新 Xcode 10 安装。
  • 在终端中,cdOrderSoupIntentStaticLib 目录。

  • 键入 make,生成:

    • 静态库 libOrderSoupIntentStaticLib.a
    • bo 输出目录中,C# 绑定定义:
      • ApiDefinitions.cs
      • StructsAndEnums.cs

OrderSoupIntentBindings 项目(依赖于此静态库及其关联的绑定定义)会自动生成这些项。 但是,手动运行上述过程将确保其按预期生成。

有关创建静态库并使用 Objective Sharpie 创建 C# 绑定定义的详细信息,请参阅绑定 iOS Objective-C 库演练。

创建绑定库

创建静态库和 C# 绑定定义后,在 Xamarin.iOS 项目中使用 Xcode 生成的意图相关代码所需的剩余部分是绑定库。

Soup Chef 存储库中,打开 SoupChef.sln 文件。 除此之外,此解决方案还包含 OrderSoupIntentBinding,这是之前生成的静态库的绑定库。

特别请注意,此项目包括:

  • ApiDefinitions.cs – Objective Sharpie 之前生成并添加到此项目的文件。 此文件的“生成操作”设置为 ObjcBindingApiDefinition

  • StructsAndEnums.cs – Objective Sharpie 之前生成并添加到此项目的另一个文件。 此文件的“生成操作”设置为 ObjcBindingCoreSource

  • libOrderSoupIntentStaticLib.a本机引用,这是之前生成的静态库。 更新本机引用属性并指定以下值:

    1. 框架 = Foundation Intents
    2. 智能链接 = On
    3. 强制加载 = On
    4. 类型 = Static

注意

ApiDefinitions.csStructsAndEnums.cs 都包含 [Watch (5,0), iOS (12,0)] 等特性。 这些由 Objective Sharpie 生成的特性已被注释掉,因为它们对于本项目来说不是必需的。

有关创建 C# 绑定库的详细信息,请参阅绑定 iOS Objective-C 库演练。

请注意,SoupChef 项目包含对 OrderSoupIntentBinding 的引用,这意味着它现在可以在 C# 中访问它所包含的类、接口和枚举:

  • OrderSoupIntent
  • OrderSoupIntentHandling
  • OrderSoupIntentResponse
  • OrderSoupIntenseResponseCode

创建 Swift 框架

默认情况下,意图定义本机代码由 Xcode 使用本机项目的语言生成。 如果在 Swift 项目中定义 Intents.intentdefinition 文件,Xcode 将生成包含所有必需类的单个 Swift 文件,可以使用该文件创建 Swift 框架。

提示

可以在 Xcode 生成设置中选择生成的意图代码所需的语言。 转到“意图目标”>“生成设置”>“意图定义编译器 - 代码生成”,然后选择 Swift 或 Objective-C。 还可以让其自动匹配目标语言。

创建 Swift 框架的过程与前面描述的过程类似:

  1. 创建新的 Swift 框架项目。
  2. 将自动生成的 Swift 文件与意图代码复制到此项目,可在此处找到此文件。
  3. 启用 Objective-C 桥接标头,因此使用 Objective-C sharpie 所需的头文件自动生成框架。

生成框架后,请按照前面所述的相同步骤创建 Xamarin 绑定。 可以在此处阅读有关为 Swift 框架创建绑定的详细信息。

将意图定义文件添加到解决方案

在 C# SoupChef 解决方案中,SoupKit 项目包含应用与其扩展之间共享的代码。 Intents.intentdefinition 文件已放置在 SoupKitBase.lproj 目录中,并且具有内容生成操作。 生成过程将此文件复制到 Soup Chef 应用捆绑包中,其中应用正常运行需要该文件。

捐赠意图

为了使 Siri 建议快捷方式,它必须先了解快捷方式何时相关。

为了让 Siri 理解这一点,每次用户下汤订单时,Soup Chef 都会向 Siri 捐赠一个意图。 基于这一捐赠(捐赠的时间、捐赠的地点、包含的参数),Siri 了解未来何时建议快捷方式。

SoupChef 使用 SoupOrderDataManager 类来放置捐赠。 当被调用为用户下汤订单时,PlaceOrder 方法又调用 DonateInteraction

void DonateInteraction(Order order)
{
    var interaction = new INInteraction(order.Intent, null);
    interaction.Identifier = order.Identifier.ToString();
    interaction.DonateInteraction((error) =>
    {
        // ...
    });
}

提取意图后,它将包装在 INInteraction 中。 INInteraction 将获得一个 与订单的唯一 ID 匹配的 Identifier(稍后删除不再有效的意图捐赠时会有所帮助)。 然后,互动被捐赠给 Siri。

order.Intent getter 的调用通过设置其 QuantitySoupOptions 和 image 来获取表示订单的 OrderSoupIntent,以及在用户录制短语以供 Siri 与意图关联时用作建议的调用短语:

public OrderSoupIntent Intent
{
    get
    {
        var orderSoupIntent = new OrderSoupIntent();
        orderSoupIntent.Quantity = new NSNumber(Quantity);
        orderSoupIntent.Soup = new INObject(MenuItem.ItemNameKey, MenuItem.LocalizedString);

        var image = UIImage.FromBundle(MenuItem.IconImageName);
        if (!(image is null))
        {
            var data = image.AsPNG();
            orderSoupIntent.SetImage(INImage.FromData(data), "soup");
        }

        orderSoupIntent.Options = MenuItemOptions
            .ToArray<MenuItemOption>()
            .Select<MenuItemOption, INObject>(arg => new INObject(arg.Value, arg.LocalizedString))
            .ToArray<INObject>();

        var comment = "Suggested phrase for ordering a specific soup";
        var phrase = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_SOUP_SUGGESTED_PHRASE", comment);
        orderSoupIntent.SuggestedInvocationPhrase = String.Format(phrase, MenuItem.LocalizedString);

        return orderSoupIntent;
    }
}

删除无效的捐赠

删除不再有效的捐赠非常重要,这样 Siri 就不会提出无益或令人困惑的快捷方式建议。

在 Soup Chef 中,可以使用“配置菜单”屏幕将菜单项标记为不可用。 Siri 不应再建议快捷方式来订购不可用的菜单项,因此 SoupMenuManagerRemoveDonation 方法会删除不再可用的菜单项的捐赠。 应用通过以下方式实现此功能:

  • 查找与现在不可用的菜单项关联的订单。
  • 获取标识符。
  • 删除具有相同标识符的交互。
void RemoveDonation(MenuItem menuItem)
{
    if (!menuItem.IsAvailable)
    {
        Order[] orderHistory = OrderManager?.OrderHistory.ToArray<Order>();
        if (orderHistory is null)
        {
            return;
        }

        string[] orderIdentifiersToRemove = orderHistory
            .Where<Order>((order) => order.MenuItem.ItemNameKey == menuItem.ItemNameKey)
            .Select<Order, string>((order) => order.Identifier.ToString())
            .ToArray<string>();

        INInteraction.DeleteInteractions(orderIdentifiersToRemove, (error) =>
        {
            if (!(error is null))
            {
                Console.WriteLine($"Failed to delete interactions with error {error.ToString()}");
            }
            else
            {
                Console.WriteLine("Successfully deleted interactions");
            }
        });
    }
}

验证成功的捐赠

该解决方案包括多个项目和特定配置。 在某些情况下,应用程序可能会因为配置不完整而出现故障,在其他情况下,应用程序可能会无提示地无法捐赠交互。 验证成功捐赠很重要,iOS 开发人员设置有助于实现此目的。 导航到“设置”>“开发人员”,然后启用以下开发人员选项以查看最近的捐赠和快捷方式:

  • 显示最近使用的快捷方式
  • 在锁屏界面上显示捐赠

启用后,每个成功的捐赠将显示在锁屏界面上和 Siri 建议选项下方。 如果在运行应用程序后没有看到捐赠,请查看以下故障排除案例:

  1. 应用无法创建 OrderSoupIntent 并出现以下错误:

    无法创建类型为“NativeLibrary.OrderSoupIntent”的本机实例:尚未加载本机类。

    此错误意味着 Xamarin 无法通过 Xamarin 绑定加载本机类。 若要解决此问题,请验证本机库是否包含绑定项目引用的所需代码,并设置正确的标志,如此处所述,请将 Force Load 标志设置为 On

  2. 应用无法初始化意图类的已加载本机实例,并出现以下错误:

    无法初始化类型为“NativeLibrary.OrderSoupIntent”的实例:本机“init”方法返回 nil。

    此问题与缺少的意图定义文件相关。 Xamarin 应用应包含 Content 类型的原始意图定义文件,如此处所述。

  3. 应用会创建意图并调用捐赠方法,而不会出现故障,但控制台输出显示有关未知意图类型的警告,并且未进行捐赠:

    无法捐赠与没有有效快捷方式类型的 OrderSoupIntent 的交互

    为了解决这个问题,必须在 plist 中正确定义意图,必须通过项目设置为当前生成配置启用并选择 Siri 权利

    应用的 info.plist

    <key>NSUserActivityTypes</key>
    <array>
        <string>ScheduleMeetingIntent</string>
    </array>
    

    具有 Siri 功能的应用的 Entitlements.plist

    <key>com.apple.developer.siri</key>
    <true/>
    

    应为目标生成配置选择自定义权利。 转到“项目设置”>“生成”>“iOS 捆绑签名”,并将“自定义权利”设置为包含所需权利的 Entitlements.plist 文件。

创建 Intents 扩展

Siri 调用意图时运行的代码放置在 Intents 扩展中,该扩展可以作为新项目添加到与现有 Xamarin.iOS 应用(如 Soup Chef)相同的解决方案中。 在 SoupChef 解决方案中,扩展称为 SoupChefIntents

SoupChefIntents – Info.plist 和 Entitlements.plist

SoupChefIntents – Info.plist

SoupChefIntents 项目中的 Info.plist捆绑标识符定义为 com.xamarin.SoupChef.SoupChefIntents

Info.plist 文件还包含以下条目:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsRestrictedWhileLocked</key>
        <array/>
        <key>IntentsSupported</key>
        <array>
            <string>OrderSoupIntent</string>
        </array>
        <key>IntentsRestrictedWhileProtectedDataUnavailable</key>
        <array/>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.intents-service</string>
    <key>NSExtensionPrincipalClass</key>
    <string>IntentHandler</string>
</dict>

在上面的 Info.plist 中:

  • IntentsRestrictedWhileLocked 列出了解锁设备时要处理的意图。
  • IntentsSupported 列出了此扩展处理的意图。
  • NSExtensionPointIdentifier 指定应用扩展的类型。 有关详细信息,请参阅 Apple 的文档
  • NSExtensionPrincipalClass 指定用于处理此扩展支持的意图的类。
SoupChefIntents – Entitlements.plist

SoupChefIntents 项目中的 Entitlements.plist 具有“应用组”功能。 此功能配置为使用与 SoupChef 项目相同的应用组:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.xamarin.SoupChef</string>
</array>

Soup Chef 使用 NSUserDefaults 保存数据。 为了在应用和应用扩展之间共享数据,它们在其 Entitlements.plist 文件中引用相同的应用组。

注意

SoupChefIntents 项目的生成配置将“自定义权利”设置为 Entitlements.plist

处理 OrderSoupIntent 后台任务

Intents 扩展根据自定义意图执行快捷方式所需的后台任务。

Siri 调用 IntentHandler 类的 GetHandler 方法(Info.plist 中定义为 NSExtensionPrincipalClass),以获取扩展 OrderSoupIntentHandling 的类的实例,该实例可用于处理 OrderSoupIntent

[Register("IntentHandler")]
public class IntentHandler : INExtension
{
    public override NSObject GetHandler(INIntent intent)
    {
        if (intent is OrderSoupIntent)
        {
            return new OrderSoupIntentHandler();
        }
        throw new Exception("Unhandled intent type: ${intent}");
    }

    protected IntentHandler(IntPtr handle) : base(handle) { }
}

共享代码的 SoupKit 项目中定义的 OrderSoupIntentHandler 实现了两个重要方法:

  • ConfirmOrderSoup – 确认是否应实际执行与意图关联的任务
  • HandleOrderSoup – 下汤订单并通过调用传入的完成处理程序来响应用户

处理打开应用的 OrderSoupIntent

应用必须正确处理未在后台运行的意图。 在 AppDelegateContinueUserActivity 方法中,这些意图的处理方式与 NSUserActivity 快捷方式相同:

public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    var intent = userActivity.GetInteraction()?.Intent as OrderSoupIntent;
    if (!(intent is null))
    {
        HandleIntent(intent);
        return true;
    }
    // ...
}  

为自定义意图提供用户接口

Intents UI 扩展为 Intents 扩展提供自定义用户接口。 在 SoupChef 解决方案中,SoupChefIntentsUI 是一个 Intents UI 扩展,它为 SoupChefIntents 提供接口。

SoupChefIntentsUI – Info.plist 和 Entitlements.plist

SoupChefIntentsUI – Info.plist

SoupChefIntentsUI 项目中的 Info.plist捆绑标识符定义为 com.xamarin.SoupChef.SoupChefIntentsui

Info.plist 文件还包含以下条目:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsSupported</key>
        <array>
            <string>OrderSoupIntent</string>
        </array>
        <!-- ... -->
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.intents-ui-service</string>
    <key>NSExtensionMainStoryboard</key>
    <string>MainInterface</string>
</dict>

在上面的 Info.plist 中:

  • IntentsSupported 指示 OrderSoupIntent 由此 Intents UI 扩展处理。
  • NSExtensionPointIdentifier 指定应用扩展的类型。 有关详细信息,请参阅 Apple 的文档
  • NSExtensionMainStoryboard 指定定义此扩展的主要接口的情节提要

SoupChefIntentsUI – Entitlements.plist

SoupChefIntentsUI 项目不需要 Entitlements.plist 文件。

创建用户界面

由于 SoupChefIntentsUIInfo.plistNSExtensionMainStoryboard 键设置为 MainInterfaceMainInterace.storyboard 文件会定义 Intents UI 扩展的接口。

在此情节提要中,有一个类型为 IntentViewController 的视图控制器。 它引用两个视图:

  • invoiceView,类型为 InvoiceView
  • confirmationView,类型为 ConfirmOrderView

注意

invoiceViewconfirmationView 的接口在 main.storyboard 中定义为辅助视图。 Visual Studio for Mac 和 Visual Studio 2017 不支持查看或编辑辅助视图;为此,请在 Xcode 的 Interface Builder 中打开 main.storyboard

IntentViewController 实现 IINUIHostedViewControlling 接口,用于在处理 Siri Intents 时提供自定义接口。 此 调用 ConfigureView 方法来自定义接口,显示确认或发票,具体取决于交互是正在确认 (INIntentHandlingStatus.Ready) 还是已成功执行 (INIntentHandlingStatus.Success):

[Export("configureViewForParameters:ofInteraction:interactiveBehavior:context:completion:")]
public void ConfigureView(
    NSSet<INParameter> parameters,
    INInteraction interaction,
    INUIInteractiveBehavior interactiveBehavior,
    INUIHostedViewContext context,
    INUIHostedViewControllingConfigureViewHandler completion)
{
    // ...
    if (interaction.IntentHandlingStatus == INIntentHandlingStatus.Ready)
    {
        desiredSize = DisplayInvoice(order, intent);
    }
    else if(interaction.IntentHandlingStatus == INIntentHandlingStatus.Success)
    {
        var response = interaction.IntentResponse as OrderSoupIntentResponse;
        if (!(response is null))
        {
            desiredSize = DisplayOrderConfirmation(order, intent, response);
        }
    }
    completion(true, parameters, desiredSize);
}

提示

有关 ConfigureView 方法的详细信息,请观看 Apple 的 WWDC 2017 演示文稿 SiriKit 中的新增功能

创建语音快捷方式

Soup Chef 提供了一个接口,用于为每个订单分配语音快捷方式,从而可以使用 Siri 订购汤。 事实上,用于录制和分配语音快捷方式的接口由 iOS 提供,并且需要很少自定义代码。

OrderDetailViewController 中,当用户点击表的“添加到 Siri”行时,RowSelected 方法将显示一个屏幕来添加或编辑语音快捷方式:

public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    // ...
    else if (TableConfiguration.Sections[indexPath.Section].Type == OrderDetailTableConfiguration.SectionType.VoiceShortcut)
    {
        INVoiceShortcut existingShortcut = VoiceShortcutDataManager?.VoiceShortcutForOrder(Order);
        if (!(existingShortcut is null))
        {
            var editVoiceShortcutViewController = new INUIEditVoiceShortcutViewController(existingShortcut);
            editVoiceShortcutViewController.Delegate = this;
            PresentViewController(editVoiceShortcutViewController, true, null);
        }
        else
        {
            // Since the app isn't yet managing a voice shortcut for
            // this order, present the add view controller
            INShortcut newShortcut = new INShortcut(Order.Intent);
            if (!(newShortcut is null))
            {
                var addVoiceShortcutVC = new INUIAddVoiceShortcutViewController(newShortcut);
                addVoiceShortcutVC.Delegate = this;
                PresentViewController(addVoiceShortcutVC, true, null);
            }
        }
    }
}

根据当前显示的订单是否存在现有语音快捷方式,RowSelected 会显示类型为 INUIEditVoiceShortcutViewControllerINUIAddVoiceShortcutViewController 的视图控制器。 在每种情况下,OrderDetailViewController 都将自己设置为视图控制器的 Delegate,这就是为什么它还实现 IINUIAddVoiceShortcutViewControllerDelegateIINUIEditVoiceShortcutViewControllerDelegate

在设备上进行测试

若要在设备上运行 Soup Chef,请按照本部分中的说明进行操作。 另请阅读有关自动预配的说明

应用组、应用 ID、预配配置文件

Apple Developer 门户的“证书、ID 和配置文件”部分中,执行以下步骤:

  • 创建一个应用组,以在 Soup Chef 应用与其扩展之间共享数据。 例如:group.com.yourcompanyname.SoupChef

  • 创建三个应用 ID:一个用于应用本身,一个用于 Intents 扩展,一个用于 Intents UI 扩展。 例如:

    • 应用:com.yourcompanyname.SoupChef

      • 对于此应用 ID,请分配 SiriKit 和“应用组”功能。
    • Intents 扩展:com.yourcompanyname.SoupChef.Intents

      • 对于此应用 ID,请分配“应用组”功能。
    • Intents UI 扩展:com.yourcompanyname.SoupChef.Intents

      • 此应用 ID 不需要特殊功能。
  • 创建上述应用 ID 后,编辑分配给应用和 Intents 扩展的“应用组”功能,并指定前面创建的特定应用组。

  • 创建三个新的开发预配配置文件,每个新应用 ID 各一个。

  • 下载这些预配配置文件,然后双击每个配置文件进行安装。 如果 Visual Studio for Mac 或 Visual Studio 2017 已在运行,请重启它以确保它注册新的预配配置文件。

编辑 Info.plist、Entitlements.plist 和源代码

在 Visual Studio for Mac 或 Visual Studio 2017 中,执行以下步骤:

  • 更新解决方案中的各种 Info.plist 文件。 将应用、Intents 扩展和 Intents UI 扩展捆绑标识符设置为前面定义的应用 ID:

    • 应用:com.yourcompanyname.SoupChef
    • Intents 扩展:com.yourcompanyname.SoupChef.Intents
    • Intents UI 扩展:com.yourcompanyname.SoupChef.Intentsui
  • 更新 SoupChef 项目的 Entitlements.plist 文件:

    • 对于“应用组”功能,请将组设置为之前创建的新应用组(在上面的示例中为 group.com.yourcompanyname.SoupChef)。
    • 确保已启用 SiriKit
  • 更新 SoupChefIntents 项目的 Entitlements.plist 文件:

    • 对于“应用组”功能,请将组设置为之前创建的新应用组(在上面的示例中为 group.com.yourcompanyname.SoupChef)。
  • 最后,打开 NSUserDefaultsHelper.cs。 将 AppGroup 变量设置为新应用组的值(例如,将其设置为 group.com.yourcompanyname.SoupChef)。

配置生成设置

在 Visual Studio for Mac 或 Visual Studio 2017 中:

  • 打开 SoupChef 项目的选项/属性。 在“iOS 捆绑签名”选项卡上,将“签名标识”设置为自动,并将“预配配置文件”设置为之前创建的特定于应用的新预配配置文件。

  • 打开 SoupChefIntents 项目的选项/属性。 在“iOS 捆绑签名”选项卡上,将“签名标识”设置为自动,并将“预配配置文件”设置为之前创建的特定于 Intents 扩展的新预配配置文件。

  • 打开 SoupChefIntentsUI 项目的选项/属性。 在“iOS 捆绑签名”选项卡上,将“签名标识”设置为自动,并将“预配配置文件”设置为之前创建的特定于 Intents UI 扩展的新预配配置文件。

完成这些更改后,应用将在 iOS 设备上运行。

自动预配

可以使用自动预配直接在 IDE 中完成其中的许多预配任务。 但是,自动预配不会设置应用组。 需要使用要使用的应用组的名称手动配置 Entitlements.plist 文件,请访问 Apple Developer 门户以创建应用组,将该应用组分配给通过自动预配创建的每个应用 ID,重新生成预配配置文件(应用、Intents 扩展、Intents UI 扩展)以包括新创建的应用组,然后下载并安装它们。