14、CarPlay仪表盘与Siri集成:仪表盘Widget开发、SiriKit意图定义、语音命令处理
说实话,CarPlay仪表盘这块,是我在车载开发中觉得最有意思的部分之一。你想想看,用户开车时最不想干的事就是盯着屏幕点点点。仪表盘Widget和Siri集成,说白了就是让用户「动口不动手」,甚至「瞄一眼就够了」。
我最早接触这个需求,是帮一家车厂做导航Widget。当时产品经理说:「用户就想在仪表盘上看到下一个转弯还有多远。」我心想这还不简单?结果一做才发现,坑不少。今天我把这些经验掰开揉碎了讲给你。
14.1 仪表盘Widget开发基础
CarPlay仪表盘,不是你想放什么就放什么。它有一套严格的模板和生命周期管理。我习惯把Widget理解成「一块会呼吸的卡片」——它得在有限的空间里,展示最核心的信息。
14.1.1 Widget的模板类型
Apple提供了几种现成的模板,我列个表给你看:
| 模板名称 | 适用场景 | 我的建议 |
|---|---|---|
| CPDashboardButtonWidget | 单个按钮操作(如回家、暂停) | 适合高频操作,别放超过3个 |
| CPDashboardManeuverWidget | 导航转向提示 | 这个我踩过坑,后面细说 |
| CPDashboardNowPlayingWidget | 当前播放媒体 | 系统自带,一般不用自己写 |
嗯,这里要注意:Widget不是独立运行的。它依附于你的主App。如果主App被系统终止了,Widget也会消失。所以你得保证App在后台活着。
14.1.2 开发一个导航Widget
我拿导航Widget举例。你需要在 CPApplicationDelegate 里注册:
- (void)application:(CPApplication *)application didConnectDashboardController:(CPDashboardController *)dashboardController {
// 创建导航Widget
CPManeuver *nextManeuver = [[CPManeuver alloc] init];
nextManeuver.instructionVariants = @[@"前方500米右转进入主路"];
nextManeuver.symbolImage = [UIImage imageNamed:@"turn_right"];
CPDashboardManeuverWidget *widget = [[CPDashboardManeuverWidget alloc] init];
widget.leadingManeuver = nextManeuver;
// 设置到仪表盘
[dashboardController setWidget:widget];
}
这段代码看起来简单,但我曾经犯过一个低级错误:忘记设置 symbolImage。结果仪表盘上只显示文字,没有图标,用户根本来不及看。你想想看,开车时谁会读「前方500米右转」?瞟一眼箭头就够了。
14.2 SiriKit意图定义
Siri集成这块,我刚开始觉得挺玄乎的。后来发现,其实就是定义「用户想干什么」,然后告诉Siri怎么处理。
14.2.1 创建自定义意图
在Xcode里新建一个 .intentdefinition 文件。我习惯把意图分成两类:
- 查询类:用户问「还有多久到家?」
- 操作类:用户说「导航到公司」
举个例子,定义一个「查询剩余里程」的意图:
// IntentDefinition文件里
// 意图名称:CheckRemainingDistance
// 参数:destination(NSString)
// 响应:distance(NSNumber), unit(NSString)
// 在代码里处理
- (void)handleCheckRemainingDistance:(CheckRemainingDistanceIntent *)intent
completion:(void (^)(CheckRemainingDistanceIntentResponse *))completion {
NSString *destination = intent.destination;
// 计算距离...
NSInteger distance = [self calculateDistanceTo:destination];
CheckRemainingDistanceIntentResponse *response =
[[CheckRemainingDistanceIntentResponse alloc] initWithCode:CheckRemainingDistanceIntentResponseCodeSuccess
userActivity:nil];
response.distance = @(distance);
response.unit = @"公里";
completion(response);
}
这里有个细节:响应里的参数名要和定义文件里完全一致。我有一回把 distance 写成了 remainingDistance,结果Siri死活不念出来。调试了半天才发现是命名不匹配。
14.2.2 意图的短语训练
定义好意图后,你得告诉Siri用户会怎么说。在 Info.plist 里配置:
<key>INIntentsSupported</key>
<array>
<dict>
<key>INIntentIdentifier</key>
<string>CheckRemainingDistance</string>
<key>INIntentPhrases</key>
<array>
<string>到{目的地}还有多远</string>
<string>距离{目的地}还有多少公里</string>
</array>
</dict>
</array>
我个人习惯至少写3-5个短语变体。因为用户说话方式千奇百怪,有人会说「还有多远到公司」,有人会说「公司还有几公里」。你覆盖得越多,Siri识别越准。
{目的地}。Siri会自动匹配用户说的内容到这个参数上。
14.3 语音命令处理
语音命令处理,说白了就是「Siri把用户的话转成意图,你的App执行意图」。但这里有个关键点:处理速度。开车时用户等不了3秒。
14.3.1 处理流程
我画了一张流程图,帮你理清整个链路:
你看,整个链路其实不长。但最容易出问题的是第三步——App处理意图。我遇到过一种情况:用户说「导航到公司」,App需要先获取当前位置,再计算路线。如果网络慢,用户会以为Siri没反应。
14.3.2 异步处理与进度反馈
我的解决方案是:先给Siri一个「正在处理」的响应。
- (void)handleNavigationToDestination:(NavigationToDestinationIntent *)intent
completion:(void (^)(NavigationToDestinationIntentResponse *))completion {
// 立即返回一个「处理中」的响应
NavigationToDestinationIntentResponse *pendingResponse =
[[NavigationToDestinationIntentResponse alloc] initWithCode:NavigationToDestinationIntentResponseCodeContinueInApp
userActivity:nil];
completion(pendingResponse);
// 然后在后台真正处理
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self calculateRouteTo:intent.destination completion:^(Route *route) {
// 处理完成后,通过CPDashboardController更新Widget
[self updateWidgetWithRoute:route];
}];
});
}
这里用到了 ContinueInApp 响应码。它的意思是:「Siri,你先别播报,让App来处理,处理完了App自己会更新界面。」这样用户不会听到「正在处理」这种废话,而是直接看到仪表盘上的路线更新了。
14.4 实战中的坑与经验
最后分享几个我踩过的坑,你遇到了可以少走弯路。
14.4.1 Widget的刷新时机
Widget不是实时刷新的。系统会按一定频率拉取数据。我一开始以为设置好数据源就行,结果发现用户开了5公里,Widget上的剩余距离还是原来的值。
解决办法:在数据变化时主动调用 [dashboardController setWidget:updatedWidget]。注意,是重新设置整个Widget,不是修改属性。
14.4.2 Siri的权限问题
Siri集成需要用户在设置里授权。如果用户没授权,你的意图永远不会被调用。我建议在App首次启动时弹窗引导用户开启:
// 检查Siri权限
[INPreferences requestSiriAuthorization:^(INSiriAuthorizationStatus status) {
if (status == INSiriAuthorizationStatusAuthorized) {
// 可以注册意图
} else {
// 引导用户去设置
}
}];
嗯,这里要注意:不要每次启动都弹窗。用户会烦的。我习惯只在第一次启动时弹,之后如果用户没授权,就在设置页面留个入口。
14.4.3 多语言支持
如果你的App要出海,Siri的短语必须本地化。中文用户说「导航到公司」,英文用户说「Navigate to office」。在 .intentdefinition 文件里,每个短语都可以设置不同语言。
我吃过这个亏。当时只写了中文短语,结果美国用户用Siri说「Go home」,App完全没反应。后来补了英文短语才解决。
好了,关于CarPlay仪表盘和Siri集成,核心内容就这些。说白了,Widget是「面子」,Siri是「里子」。面子要简洁直观,里子要快速响应。两者配合好了,用户才会觉得这车机「好用」。