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 选项。
根据上面的应用描述,我们有一个带有 main-page.js/xml、cars-page.js/xml、hotels-page.js/xml 和 details-page.js/xml 的应用。我们也有一些酒店数据,这些数据将保存在 hotels-view-model.js 文件中。
为了保持简单,专注于深度链接功能,以下是如何显示页面
如你所见,打开应用后,我们将打开主页面,并使用按钮可以导航到汽车或酒店部分。从酒店页面,我们可以点击一个酒店项目,这将引导我们进入其详细信息页面。在酒店页面上,你可以注意到每个酒店项目都有一个“添加”按钮,这会使该酒店成为我们的收藏酒店。
现在让我们深入了解实现。
在本节中,我们将为应用的两个主要导航点创建两个用户活动 - 酒店页面和汽车页面。如果足够多的最终用户浏览,即使应用没有安装在用户的设备上,这些用户活动也将作为聚光灯中的搜索结果可用。
图像在深入研究代码之前,我们需要添加一些图像,这些图像将作为用户活动搜索结果的缩略图显示。正如苹果文档建议的那样,对于一个聚光灯项目,我们需要两个图像 - 一个 40x40 和一个 80x80。
以下是酒店和汽车项目的图像
好吧,好吧,我承认我不是毕加索,也不是梵高,但对于我们的演示目的,这些图像已经足够了。:)
正如 NativeScript 文档建议的那样,让我们将这些图像作为资源添加到项目中。这意味着
将它们放在[你的项目名称]\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 搜索结果中,以便我们拥有该酒店的简洁快捷方式。假设我们处理了该按钮的点击事件,以下是实现方式
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 - 标题、描述、图像。这一次,您会注意到我们正在为属性集设置两个新属性:supportsPhoneCall 和 phoneNumbers。它们将允许我们直接从 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”,您应该会看到此屏幕,其中包含来自已索引的用户活动和核心聚焦项的结果。
这是一个功能强大的功能,可以用于应用程序的每一个方面,即使是复杂的应用程序。您可以将深度链接功能添加到您的 NativeScript 应用程序,并告知我们您取得了什么成就。
快乐编码!