什么是CarPlay?
CarPlay是苹果公司的iOS汽车集成标准,它允许你将iPhone的内容显示在你兼容的汽车主机上,并控制你的手机。这方面的常见用途包括从Spotify或苹果音乐等服务中播放音乐,或使用地图应用程序进行旅行导航。
随着CarPlay在每个iOS版本中的进步,越来越多的应用程序类别被添加进来,为更多的第三方应用程序在汽车主机上的创建和访问打开了大门。最近增加的类别包括快餐订购应用程序和电动车充电发现应用程序,这意味着CarPlay正变得比以往任何时候都更容易获得,并被添加到越来越多的应用程序。
CarPlay是如何工作的?
CarPlay提供了一系列模板,开发者可以使用这些模板来显示应用数据。这些模板具有很强的意见性,并确保供应商的用户界面可以在汽车内安全使用。这些模板的可用性根据你所开发的应用程序的类型而有所不同。例如,一个导航应用程序可以利用大量的模板,包括地图控件、列表和网格。相比之下,一个快餐订购的应用程序被限制在使用一个较小的模板子集。对于高度专业化的应用程序类型,如电动车充电供应商,我们提供了一个特殊的兴趣点模板,它可以同时为你在地图和列表上显示多个点。
CarPlay和React Native
在React Native中使用CarPlay是一个相当新的概念,但在npm上有一个名为react-native-carplay
的包,在这个领域取得了很大的进展。这个库的React Native部分是用Typescript编写的,它提供了适当的类型供你的应用程序使用。该包允许你创建大多数支持的模板,并通过推送和弹出模板来控制CarPlay的显示栈。此外,它还提供了CarPlay连接/断开的钩子,允许你在你的应用程序中做出相应的反应。
设置这个包需要一点本地代码,但一旦完成,一切都应该在React Native方面完成。你可以在软件包的readme中找到这些步骤的完整说明。由于这个原因,react-native-carplay
,目前不支持在托管的expo
环境中,因为这种设置不允许你自己编辑任何本地代码,而不首先从托管的工作流中弹出。
当希望将CarPlay应用程序发布到应用商店时,你必须向苹果公司申请适当的权利。然而,在开发过程中,你可以使用内置的CarPlay模拟器。 最新的iOS 14模拟器似乎功能齐全,并且与物理CarPlay装置的行为几乎相同。
将CarPlay集成到你的React Native应用程序中
当把CarPlay添加到现有的React Native应用程序时,你真的希望保持手机和CarPlay屏幕的同步性。这将允许你在连接/断开与CarPlay系统的连接时在两者之间进行交接。例如,如果你在手机上搜索了一首歌曲,然后连接到CarPlay,你会希望CarPlay在屏幕上以你选择的歌曲开始。出于这个原因,最好为您的屏幕创建适当的CarPlay模板,其位置与已配对的手机屏幕代码相同,在 useEffect
或类似的地方。
添加一个模板
让我们先在React Native应用程序中添加一个简单的CarPlay菜单。当使用CarPlay显示栈时,你必须始终有一个root
模板。这是位于显示堆栈底部的基本模板,通常采用的形式是 GridTemplate
或 MapTemplate
的形式,取决于应用程序的类型。一个网格可以显示多达8个按钮以及屏幕顶部的条形按钮,用于快速链接到设置/音量/语音控制等:
import React, { useEffect } from 'react';
import { View, Text } from 'react-native';
import { CarPlay, GridTemplate } from 'react-native-carplay';
export function Index() {
useEffect(() => {
const gridTemplate = new GridTemplate({
buttons: [
{
id: item0,
titleVariants: ['Item 0],
image: require('images/button.png'),
},
{
id: 'item1',
titleVariants: ['Item 1],
image: require('images/button.png'),
}
// ...
],
title: 'Grid Template',
});
CarPlay.setRootTemplate(gridTemplate);
}, []);
return (
<View>
<Text>Hello, world</Text>
</View>
);
}
CarPlay.setRootTemplate
在这里你可以看到,我们已经创建了一个新的GridTemplate
,并在useEffect
。这确保了这段代码只被调用一次,不会在每次重新渲染反应组件时重新创建CarPlay模板,因为没有依赖性传递给useEffect
。
处理连接/断开连接
当使用CarPlay时,我们只想在连接时渲染模板,否则我们很容易导致错误。react-native-carplay
,我们可以在CarPlay类上注册一个连接回调,以发出连接信号,并在那时渲染我们的模板。我们可以将连接状态作为一个依赖项添加到useEffect
,以确保它在连接状态改变时运行:
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import { CarPlay, GridTemplate } from 'react-native-carplay';
export function Index() {
// default the carplayConnected value to the connected state of the CarPlay class
const [carPlayConnected, setCarPlayConnected] = useState(CarPlay.connected);
useEffect(() => {
function onConnect() {
setCarPlayConnected(true);
}
function onDisconnect() {
setCarPlayConnected(false);
}
// register connect and disconnect callbacks
CarPlay.registerOnConnect(onConnect);
CarPlay.registerOnDisconnect(onDisconnect);
return () => {
// unregister the callbacks in the return statement
CarPlay.unregisterOnConnect(onConnect);
CarPlay.unregisterOnDisconnect(onDisconnect);
};
});
useEffect(() => {
// only create the template if connected
if (carPlayConnected) {
const gridTemplate = new GridTemplate({
buttons: [
{
id: 'List',
titleVariants: ['List'],
image: require('images/button.png'),
},
{
id: 'Grid',
titleVariants: ['Grid'],
image: require('images/button.png'),
}
],
title: 'Hello, world',
});
CarPlay.setRootTemplate(gridTemplate);
// clean up the grid template in the return statement
// this will only be run when carplayconnected changes
// to false, or the component is unmounted
return () => {
gridTemplate = undefined;
}
}
}, [carPlayConnected]);
return (
<View>
<Text>Hello, world</Text>
</View>
);
}
在上面的例子中,我们同时使用了CarPlay类的connected
状态和registerOnConnect
函数,以确保我们的网格模板在连接和随后从CarPlay显示器上断开时被创建和拆下。将carPlayConnected
作为第二个useEffect
的依赖关系,确保每当值发生变化时,该函数就会运行。在更复杂的例子中,你可能需要在渲染之间使用 useRef
钩子。在这种情况下,你的useEffect
的返回语句更加重要,因为这是你要删除你的引用并执行任何必要的清理操作的地方。
更新模板
一些模板,如ListTemplate
,一旦被创建,你就可以通过一个api来更新它们。例如,一个列表模板可以通过一个updateSections
函数来更新它的项目,其中部分是项目的分组数组。下面的例子显示了当新的搜索结果出现时你如何更新列表项。它利用了useRef
钩子,以确保每次渲染都使用同一个列表模板:
import React, { useEffect, useRef, useCallback } from 'react';
import { View } from 'react-native';
import { CarPlay, ListTemplate } from 'react-native-carplay';
export function Index() {
const listTemplate = useRef<ListTemplate>();
useEffect(() => {
listTemplate.current = new ListTemplate({
title: 'Results',
sections: []
});
CarPlay.pushTemplate(listTemplate.current);
return () => {
listTemplate.current = undefined;
}
}, []);
const onResults = useCallback((results) => {
listTemplate.current.updateSections(results);
}, [listTemplate]);
return (
<View>
<Search onResults={onResults}/>
</View>
);
}
updateSections
正如你在上面的例子中看到的,onResults
回调正在调用listTemplate
的函数,而这个函数又在CarPlay屏幕上显示这些新的列表项。创建一个列表模板的引用,并以这种方式调用更新函数,意味着它将不必在每次变化时重新创建一个全新的模板,并为用户提供更好的体验。关于每个模板可以实现的更多信息,可以在苹果CarPlay文档中找到。在这一点上,并不是所有的功能都在react-native-carplay
,但随着时间的推移,它正在变得更好,我们SitePen正在积极为它的发展作出贡献。
结论
将CarPlay添加到你的应用程序将帮助它在没有CarPlay支持的其他应用程序中脱颖而出。它正被越来越多的汽车制造商所采用,与它在安卓手机上的主要竞争对手Android Auto相比,它的第三方API接入更快。苹果正在为CarPlay添加更多的功能和应用类型,每一次发布都使它成为考虑为你的应用添加支持的最佳时机。
直到最近,将CarPlay支持添加到react native应用程序中意味着自己要编写大量的本地代码,但react-native-carplay
包为你解决了这个问题,并为你提供了一个类型安全的库,用于你的React Native应用程序。一个更完整的应用实例可以在react-native-carplay
GitHub仓库的/example
目录下找到。