返回博客主页
← 所有帖子

使用 iOS 9 用户活动和核心聚光灯 API 深度链接你的 NativeScript 应用

2015 年 8 月 28 日 — 作者:Nikolay Diyanov

iOS 9(Beta 版)已经发布一段时间了,为各种开发者(游戏或商业应用开发者)带来了很多很棒的新功能。

iOS 9 最大的改进之一(如果不是最大的话!),毫无疑问是现在向开发者开放的深度链接功能。使用聚光灯搜索,你的最终用户现在可以搜索你的应用的内容,即使你的应用没有安装在他们的设备上,前提是其他用户已经安装了你的应用并浏览了相同的内容。或者,你可以将某些应用内容设置为私有,并且仅在搜索中对相应的用户可用。苹果最近发布了一篇相当全面的指南,介绍了可用的搜索 API,其中还提供了一些关于可能的搜索 API 用法的示例。

当然,这些 API 在 NativeScript 中可用,今天我们将实现一个提供深度链接功能的 NativeScript 应用。

我们今天的场景

我想向你展示如何使用两个可能的 API 来将你的应用内容公开给聚光灯搜索。该应用将是一个简单的服务应用,提供酒店服务和汽车服务。从酒店页面,我们可以导航到特定酒店的详细信息页面。同样,从酒店页面,我们将有一个按钮将酒店标记为我们的收藏酒店。

得益于第一个 API,称为用户活动,我们将向聚光灯搜索公开应用的主要导航点 - 酒店页面和汽车页面。正如你稍后将看到的那样,这些点可能会对没有安装应用的用户可用,前提是已经有一些用户访问了这些导航点。

得益于第二个 API,称为核心聚光灯,我们将获取最终用户标记为收藏的酒店,并将其公开给聚光灯搜索,以便他们可以轻松访问其收藏的住宿设施。

先决条件

要能够遵循本文,你应该使用最新版本的 NativeScript 及其 iOS 运行时。以下是在 Mac 上使用终端检查 NativeScript 版本的方法

tns --version //应返回 1.2.3 或 1.2.4


注意:“tns”命令是输入“nativescript”的快捷方式。在这篇文章中,我将使用“tns”,但你也可以使用“nativescript”来获得相同的结果

假设你已经运行了 tns platform add ios 命令将平台特定的 ios 文件添加到项目,则应用主目录中的 package.json 文件应为 NativeScript iOS 运行时声明 1.2.2

"tns-ios": {
"version": "1.2.2"
}

我们还需要确保 NativeScript CLI 使用 iOS9 和 Xcode 7(Beta 版)命令行工具构建应用。为此,启动 Xcode 7 或 Xcode 6(任何一个都可以),然后从顶部菜单中,转到 Xcode >> 首选项。在首选项窗口中,选择位置选项卡,然后从命令行工具下拉菜单中选择 Xcode 7.0 选项。

spotlight-commandlinetools

应用实现

应用结构

根据上面的应用描述,我们有一个带有 main-page.js/xml、cars-page.js/xml、hotels-page.js/xml 和 details-page.js/xml 的应用。我们也有一些酒店数据,这些数据将保存在 hotels-view-model.js 文件中。

spotlight-file-structure

为了保持简单,专注于深度链接功能,以下是如何显示页面

spotlight-main-page  spotlight-cars-page    spotlight-hotels-page spotlight-details-page

 

如你所见,打开应用后,我们将打开主页面,并使用按钮可以导航到汽车或酒店部分。从酒店页面,我们可以点击一个酒店项目,这将引导我们进入其详细信息页面。在酒店页面上,你可以注意到每个酒店项目都有一个“添加”按钮,这会使该酒店成为我们的收藏酒店。

现在让我们深入了解实现。

创建用户活动项目

在本节中,我们将为应用的两个主要导航点创建两个用户活动 - 酒店页面和汽车页面。如果足够多的最终用户浏览,即使应用没有安装在用户的设备上,这些用户活动也将作为聚光灯中的搜索结果可用。

图像

在深入研究代码之前,我们需要添加一些图像,这些图像将作为用户活动搜索结果的缩略图显示。正如苹果文档建议的那样,对于一个聚光灯项目,我们需要两个图像 - 一个 40x40 和一个 80x80。

以下是酒店和汽车项目的图像

hotels@2x  hotels    cars@2x  cars

 

好吧,好吧,我承认我不是毕加索,也不是梵高,但对于我们的演示目的,这些图像已经足够了。:)

正如 NativeScript 文档建议的那样,让我们将这些图像作为资源添加到项目中。这意味着

  • 首先为它们命名,例如:hotels.png 和 [email protected]
  • 将它们放在[你的项目名称]\app\App_Resources\iOS 文件夹中

实现

此外,假设我们使用以下简单的 xml,使用 pageLoaded 事件处理程序将 cars 页面的加载事件附加到一起:

<Page style="background-color: white;" xmlns="http://www.nativescript.org/tns.xsd" loaded="pageLoaded">
  <StackLayout>
    <Label text="Cars" cssClass="title"/>
  </StackLayout>
</Page>

让我们看看创建用户活动项目的代码应该是什么,并相应地讨论它

function pageLoaded(args) {
    frameModule.topmost().ios.navBarVisibility = "always";
 
    var page = args.object;
    page.ios.title = "Cars";
 
    var carsImg = imageSourceModule.fromResource("cars").ios;
    var carsData = UIImagePNGRepresentation(carsImg);
    var carsImgData = NSData.dataWithData(carsData);
 
    var carsAttributeSet = CSSearchableItemAttributeSet.alloc().initWithItemContentType(kUTTypeItem);
    carsAttributeSet.title = "Cars";
    carsAttributeSet.contentDescription = "现在租车,开车上路!";
    carsAttributeSet.keywords = ["租车", "汽车", "租赁", "车辆"];
    carsAttributeSet.thumbnailData = carsImgData;
 
    var activity = NSUserActivity.alloc().initWithActivityType("com.myCompany.services");
    activity.title = "Cars";
    activity.userInfo = {[ {"id": "carsID"} ]};
    activity.contentAttributeSet = carsAttributeSet;
    activity.eligibleForSearch = true;
    activity.eligibleForPublicIndexing = true;
    activity.becomeCurrent();
}

在用户活动实现的第一部分中,我们从资源中获取了相应的图像,并使用一些本地方法将其准备用于用户活动项目

var carsImg = imageSourceModule.fromResource("cars").ios;
var carsData = UIImagePNGRepresentation(carsImg);
var carsImgData = NSData.dataWithData(carsData);

在下一部分,我们将准备 CSSearchableItemAttributeSet,它在项目在搜索结果中的显示方式中起着至关重要的作用。正如您在下面看到的,我们正在设置标题、描述、关键词和缩略图。kUTTypeItem 是一个特殊的常量类型,它定义了项目在 Spotlight 搜索结果中的显示方式。有关可用常量的更多信息,您可以参考 Apple 文档

var carsAttributeSet = CSSearchableItemAttributeSet.alloc().initWithItemContentType(kUTTypeItem);
carsAttributeSet.title = "汽车";
carsAttributeSet.contentDescription = "立即租车,开始驾驶!";
carsAttributeSet.keywords = ["租车", "汽车", "租赁", "车辆"];
carsAttributeSet.thumbnailData = carsImgData;

最后是用户活动项目本身。我们正在设置它的类型 - 一种标识符,标题,用户信息,它又是另一个标识符,我们还将该用户活动与 CSSearchableItemAttributeSet 关联。以下两个重要属性。eligibleForSearch 允许该用户活动在 Spotlight 搜索结果中出现,eligibleForPublicIndexing 允许该用户活动出现在未安装该应用程序的最终用户的 Spotlight 搜索结果中。请注意,为了实现这一点,用户活动应该受到安装了该应用程序的其他用户的极大关注。最后但并非最不重要的一点是,我们应该调用 becomeCurrent 方法将活动提交到 Spotlight 索引:

var activity = NSUserActivity.alloc().initWithActivityType("com.myCompany.services");
activity.title = "汽车";
activity.userInfo = {[ {"id": "carsID"} ]};
activity.contentAttributeSet = carsAttributeSet;
activity.eligibleForSearch = true;
activity.eligibleForPublicIndexing = true;
activity.becomeCurrent();

酒店用户活动代码几乎相同,但当然具有相应的关键词、标题、描述等

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = model;
 
    frameModule.topmost().ios.navBarVisibility = "always";
 
    var page = args.object;
    page.ios.title = "酒店";
 
    var hotelsImg = imageSourceModule.fromResource("hotels").ios;
    var hotelsData = UIImagePNGRepresentation(hotelsImg);
    var hotelsImgData = NSData.dataWithData(hotelsData);
 
    var hotelsAttributeSet = CSSearchableItemAttributeSet.alloc().initWithItemContentType(kUTTypeItem);
    hotelsAttributeSet.title = "酒店";
    hotelsAttributeSet.contentDescription = "立即预订您的房间!";
    hotelsAttributeSet.keywords = ["住宿", "酒店", "预订", "入住"];
    hotelsAttributeSet.thumbnailData = hotelsImgData;
 
    var activity = NSUserActivity.alloc().initWithActivityType("com.myCompany.services");
    activity.title = "酒店";
    activity.userInfo = { "id": "hotelsID" };
    activity.contentAttributeSet = hotelsAttributeSet;
    activity.eligibleForSearch = true;
    activity.eligibleForPublicIndexing = true;
    activity.becomeCurrent();
}


以下是搜索“住宿”的“acc”的结果

spotlight-user-activity

 
创建核心 Spotlight 项目

正如我上面所讨论的,在酒店页面,您将获得酒店列表,点击列表中的酒店,您将导航到包含有关该酒店更多详细信息的页面。在酒店页面上,每个酒店项目都有一个“添加到收藏夹”按钮。为了简单起见,我们不会保留“收藏夹”状态,但当我们点击该按钮时,相应的酒店将被添加到 Spotlight 搜索结果中,以便我们拥有该酒店的简洁快捷方式。假设我们处理了该按钮的点击事件,以下是实现方式

function favButtonTap(args) {
 
    var hotelItem = args.object.bindingContext;
 
    var hotelImg = hotelItem.hotelImage.ios
    var hotelMidImgData = UIImagePNGRepresentation(hotelImg);
    var hotelImgData = NSData.dataWithData(hotelMidImgData);
 
    var defaultSearchableIndex = CSSearchableIndex.defaultSearchableIndex();
 
    var hotelAttributeSet = CSSearchableItemAttributeSet.alloc().initWithItemContentType(kUTTypeContact);
 
    // 设置描述项目属性的属性,例如标题、描述和图像。
    hotelAttributeSet.title = hotelItem.hotelText;
    hotelAttributeSet.contentDescription = hotelItem.hotelDescription + " 立即预订您的房间!";
    hotelAttributeSet.keywords = ["住宿", "酒店", "预订", "入住", hotelItem.hotelText, hotelItem.hotelDescription];
    hotelAttributeSet.thumbnailData = hotelImgData;
    hotelAttributeSet.supportsPhoneCall = 1;
    hotelAttributeSet.phoneNumbers = [hotelItem.hotelPhoneNumber];
     
    // 创建一个可搜索项目,指定其 ID、关联域和您之前创建的属性集。
    var hotelCSItem = CSSearchableItem.alloc().initWithUniqueIdentifierDomainIdentifierAttributeSet(hotelItem.id, "org.NativeScript.Deeplinking", hotelAttributeSet);
 
    // 索引项目。
    defaultSearchableIndex.indexSearchableItemsCompletionHandler([hotelCSItem], function (error) {} );   
}

ListView 为每个项目创建了一个 bindingContext,我们从中受益,可以轻松地使用特定于“收藏夹”酒店的数据填充 CSSearchableItemAttributeSet - 标题、描述、图像。这一次,您会注意到我们正在为属性集设置两个新属性:supportsPhoneCallphoneNumbers。它们将允许我们直接从 Spotlight 搜索结果中拨打目标酒店的电话,如下面的图所示

spotlight-core-spotlight

响应 Spotlight 结果选择

首先,我需要说几句话关于位于 tns_modules 文件夹中的应用程序模块。该模块包含对应用程序生命周期方法至关重要,这些方法在应用程序运行/暂停/恢复等时被调用。特别是对于 iOS 平台,您可以在 [您的项目文件夹]\app\tns_modules\application\application.ios.js 中找到该模块

随着 iOS8 中手势 API 的推出,Apple 引入了一种方法,该方法在使用 Spotlight 搜索启动应用程序或从一台设备继续到另一台设备时被调用。该方法名为 applicationContinueUserActivityRestorationHandler。我们将添加其定义到 NativeScript 应用程序模块,版本为 1.3(将于 9 月/10 月发布)。目前,您可以在 application.ios.js 文件中手动将其包含在其他方法中,如下所示

TNSAppDelegate.prototype.applicationContinueUserActivityRestorationHandler = function (application, userActivity, restorationHandler) {
        exports.notify({ eventName: "applicationContinueUserActivityRestorationHandler", object: this, application: application, userActivity: userActivity, restorationHandler: restorationHandler  });
};

正如您注意到的,我们正在导出一个事件,该事件将在调用该方法时触发。这将允许我们在 app.js 文件中处理事件。以下是操作方法

application.addEventListener("applicationContinueUserActivityRestorationHandler", function(args) {
     
    console.log("正在恢复应用程序以导航到页面");
 
    var userActivity = args.userActivity;
 
    var frameModule = require("ui/frame");
    var topMost = frameModule.topmost();
 
    if (userActivity.activityType == "com.myCompany.services") {
        if (userActivity.userInfo.objectForKey("id") == "hotelsID") {
            topMost.navigate({
                moduleName: "hotels-page"
            });
        }
        else if (userActivity.userInfo.objectForKey("id") == "carsID") {
            topMost.navigate({
                moduleName: "cars-page"
            });
        }
    }
 
    if (userActivity.activityType == CSSearchableItemActionType) {
        var uniqueIdentifier = userActivity.userInfo.objectForKey(CSSearchableItemActivityIdentifier);
         
        for (i = 0; i<model.hotels.length;i++) {
            if (uniqueIdentifier == model.hotels.getItem(i).id) {
                topMost.navigate({
                    moduleName: "details-page",
                    context: model.hotels.getItem(i)
                });
            }
        }
    };
 
    return true;
});

如上所示,我们检查调用恢复处理程序的活动的类型和 ID(点击的聚焦项),并根据它们的类型,我们导航到相应的页面 - 导航到酒店/汽车页面或加载有关特定酒店信息的详细信息页面。

运行应用程序

我们准备好了。运行tns run ios --emulator命令,应用程序通常会打开(这会将我们的聚焦项添加到全局聚焦索引中)。按 CMD + SHIFT + H 将应用程序置于后台或将其关闭,然后从上到下绘制屏幕以触发聚焦搜索。键入“hot”,您应该会看到此屏幕,其中包含来自已索引的用户活动和核心聚焦项的结果。

spotlight-user-activity-core-spotlight

这是一个功能强大的功能,可以用于应用程序的每一个方面,即使是复杂的应用程序。您可以将深度链接功能添加到您的 NativeScript 应用程序,并告知我们您取得了什么成就。

获取演示应用程序的完整源代码在 GitHub 上.

快乐编码!