如何将CarPlay添加到你的React Native应用中

327 阅读7分钟

什么是CarPlay?

CarPlay是苹果公司的iOS汽车集成标准,它允许你将iPhone的内容显示在你兼容的汽车主机上,并控制你的手机。这方面的常见用途包括从Spotify或苹果音乐等服务中播放音乐,或使用地图应用程序进行旅行导航。

随着CarPlay在每个iOS版本中的进步,越来越多的应用程序类别被添加进来,为更多的第三方应用程序在汽车主机上的创建和访问打开了大门。最近增加的类别包括快餐订购应用程序和电动车充电发现应用程序,这意味着CarPlay正变得比以往任何时候都更容易获得,并被添加到越来越多的应用程序。

CarPlay Development

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 模板。这是位于显示堆栈底部的基本模板,通常采用的形式是 GridTemplateMapTemplate的形式,取决于应用程序的类型。一个网格可以显示多达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 目录下找到。