先日、AirPodsの回転角を監視するiOSアプリのプロトタイプをReact Nativeで作りました。
AirPodsの回転角を監視して、悪い姿勢が続いた際に悲鳴が流れるiOSアプリをつくりました。名付けて「腰の悲鳴」です。 pic.twitter.com/ZqwNDjSRH6
— 君塚史高 (@ki_230) 2024年1月11日
普段はExpoを使ってアプリを作っているのですが、react-native-headphone-motionのExpo用のモジュールが見当たらなかったため、今回はExpoを挟まず、直接React Nativeで制作しました。
ここ数日React Nativeの記事を書いているのは、その際に調べたことの備忘録です。
今回は、react-native-headphone-motionの導入手順を記しておきます。
リポジトリのexsampleが非常にわかりやすいので助かりました。
アプリの作成
npx react-native@latest init ReactNativeHeadphoneMotion
react-native-headphone-motionの導入
yarn add react-native-headphone-motion
npx pod-install
Info.plistを編集
ios/アプリ名/Info.plistに、
<key>NSMotionUsageDescription</key> <string>The description will be shown under the permission dialog</string>
を追記します。
僕の環境だと、
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleDisplayName</key> <string>ReactNativeHeadphoneMotion</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>$(MARKETING_VERSION)</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>$(CURRENT_PROJECT_VERSION)</string> <key>LSRequiresIPhoneOS</key> <true/> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSAllowsLocalNetworking</key> <true/> </dict> <key>NSLocationWhenInUseUsageDescription</key> <string></string> <key>UILaunchStoryboardName</key> <string>LaunchScreen</string> <key>UIRequiredDeviceCapabilities</key> <array> <string>armv7</string> </array> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> <key>UIViewControllerBasedStatusBarAppearance</key> <false/> <key>NSMotionUsageDescription</key> <string>The description will be shown under the permission dialog</string> </dict> </plist>
となりました。
App.tsxを編集
動作確認のために、デバイスモーションの更新があった際に値を更新するアプリを書いてみます。
import React, { useEffect, useState, } from 'react'; import {Button, SafeAreaView, StyleSheet, Text} from 'react-native'; import { requestPermission, onDeviceMotionUpdates, startListenDeviceMotionUpdates, stopDeviceMotionUpdates, } from 'react-native-headphone-motion'; export default function App() { const [pitch, setPitch] = useState(0); const [pitchDeg, setPitchDeg] = useState(0); const [roll, setRoll] = useState(0); const [rollDeg, setRollDeg] = useState(0); const [yaw, setYaw] = useState(0); const [yawDeg, setYawDeg] = useState(0); const [gravityX, setGravityX] = useState(0); const [gravityY, setGravityY] = useState(0); const [gravityZ, setGravityZ] = useState(0); const [rotationRateX, setRotationRateX] = useState(0); const [rotationRateY, setRotationRateY] = useState(0); const [rotationRateZ, setRotationRateZ] = useState(0); const [userAccelerationX, setUserAccelerationX] = useState(0); const [userAccelerationY, setUserAccelerationY] = useState(0); const [userAccelerationZ, setUserAccelerationZ] = useState(0); useEffect(() => { const handleDeviceMotionUpdates = onDeviceMotionUpdates((data) => { setPitch(data.attitude.pitch); setPitchDeg(data.attitude.pitchDeg); setRoll(data.attitude.roll); setRollDeg(data.attitude.rollDeg); setYaw(data.attitude.yaw); setYawDeg(data.attitude.yawDeg); setGravityX(data.gravity.x); setGravityY(data.gravity.y); setGravityZ(data.gravity.z); setRotationRateX(data.rotationRate.x); setRotationRateY(data.rotationRate.y); setRotationRateZ(data.rotationRate.z); setUserAccelerationX(data.userAcceleration.x); setUserAccelerationY(data.userAcceleration.y); setUserAccelerationZ(data.userAcceleration.z); }); return () => { handleDeviceMotionUpdates.remove(); }; }, []); return ( <SafeAreaView style={styles.container}> <Button title={'requestPermission'} onPress={async () => { await requestPermission(); }} /> <Button title={'startListenDeviceMotionUpdates'} onPress={async () => { await startListenDeviceMotionUpdates(); }} /> <Button title={'stopDeviceMotionUpdates'} onPress={async () => { await stopDeviceMotionUpdates(); }} /> <Text>{`pitch: ${pitch}`}</Text> <Text>{`pitchDeg: ${pitchDeg}`}</Text> <Text>{`roll: ${roll}`}</Text> <Text>{`rollDeg: ${rollDeg}`}</Text> <Text>{`yaw: ${yaw}`}</Text> <Text>{`yawDeg: ${yawDeg}`}</Text> <Text>{`gravityX: ${gravityX}`}</Text> <Text>{`gravityY: ${gravityY}`}</Text> <Text>{`gravityZ: ${gravityZ}`}</Text> <Text>{`rotationRateX: ${rotationRateX}`}</Text> <Text>{`rotationRateY: ${rotationRateY}`}</Text> <Text>{`rotationRateZ: ${rotationRateZ}`}</Text> <Text>{`userAccelerationX: ${userAccelerationX}`}</Text> <Text>{`userAccelerationY: ${userAccelerationY}`}</Text> <Text>{`userAccelerationZ: ${userAccelerationZ}`}</Text> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', }, });
これでアプリを実行し「requestPermission」をタップすると、初回にパーミッションを求めるアラートが表示されます。
パーミッションを許可した後、AirPodsをiPhoneに接続し、「startListenDeviceMotionUpdates」をタップすると、AirPodsのモーションデータが数値で表示されるはずです。