VisionOS に React Native の様々な UI コンポーネントをだしてみる
さて、一応 React Native で VisionOS 向けのアプリを表示するところまで作れたので、続いてはいろいろなコンポーネントを表示させ、どんな見た目になるか、利用は可能かどうかなどをみていこう
ちなみに react-native-visionos のシリーズは3回目。前回までは以下
- Vision Pro 向けアプリを react-native-visionos で作ってあそぶ - よりひろい フロントエンド
- react-native-visionos でつくるVisionOS向けアプリの初期構成 - よりひろい フロントエンド
ちなみに、改めて第1回目で作ったアプリを起動しようとしたら main.jsbundle
がプロジェクトに存在するにもかかわらず同様のエラーが出た。
新しくプロジェクトを作り直したり main.jsbundle
を作り直したりしたが、エラー表示は消えない。そこで Xcode プロジェクトに main.jsbundle
を追加したところ、無事に表示されるようになった。
いろいろな UI コンポーネントを表示させてみる
React Native には標準で様々な UI コンポーネントが用意されている。どんな UI コンポーネントがあるかは公式サイトにて確認できる。
今回は、よく使いそうな主要なコンポーネントを中心にいろいろウインドウに配置してみた。iOS の表示と微妙に異なるものもあり、実際に配置してみて挙動を確認するのが良いように思う。
今回はレイアウトについても確認したいので Flexbox によるレイアウトも行ってみた。レイアウトについては、あまり心配せず行えるが、何も指定せず UI コンポーネントをウインドウに配置すると、左上の角丸で一部が隠れてしまうという問題があった。これについては気をつけたい。
テキスト装飾などもスタイルを適用してもその通りに表示されるので、心配はいらなそうだ。ただし、全て試してみたわけではないので今後何か問題が起きるかもしれないという点は踏まえつつ使っていける。
画像の表示にはちょっとクセあり?
今回 main.jsbundle
のプラットフォーム指定は ios
を指定している。これは visionos
だとエラーになるためで、理由としては Image.ios.tsx
というファイルは存在しているが Image.visionos.tsx
がまだ存在していないためであろうと推測される。この点については今後修正されていく気がするのであまり心配はしていない。
そのため現時点では iOS 向けのビルドを一度行わないと画像の表示ができなかった (というように僕は認識しているが違うかもしれない) のが注意点だ。これを行わないと VisionOS 上では何も表示されないエリアが出力されるのみとなる。
ボタンも2種類表示してみた
ボタンは、普通のボタンと Pressable というコンポーネントで作ることが出来るタイプの2種類を作ってみたが、いずれも機能した。Pressable というのは比較的新しいコンポーネントらしく、細かなスタイルのコントロールが行える。
ボタンを押すと、単なるアラートを出すようにもしている。VisionOS は単なるアラートでも結構かっこいいというのが感想である。
useState も使う
React といえば hooks で各種状態のコントロールや様々な機能追加をコンポーネントに行うが、今回は試しに一番平易な useState を使ったスイッチの状態管理をしてみた。こちらも特に問題なく無事に動作した。
初期状態から変更を行なったコード
今回いろいろな UI コンポーネントを追加したコードは以下。App.tsx
に直接書いてしまっているが、もっとちゃんとしたアプリを作る際にはフォルダ構成を変えることになるだろうと思う。また現状は App.tsx
がルート直下にあるが src
ディレクトリを作って管理していく想定だ。
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import React, {useState} from 'react';
import type {PropsWithChildren} from 'react';
import {
ScrollView,
StyleSheet,
Text,
useColorScheme,
View,
ActivityIndicator,
Button,
Alert,
Pressable,
TextInput,
Switch,
Image,
} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
const profileImage = require('./assets/karad_2016.jpg');
type SectionProps = PropsWithChildren<{
title: string;
}>;
function Section({children, title}: SectionProps): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
}
function App(): React.JSX.Element {
const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => setIsEnabled(previousState => !previousState);
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
{/* 画像の表示 */}
<Section title="Image">
<View style={{flexDirection: 'row'}}>
<Image style={{width: 64, height: 64}} source={profileImage} />
</View>
</Section>
{/* 単なるテキストの表示 */}
<Section title="Text">
<View style={{flexDirection: 'row'}}>
<Text style={{color: 'white'}}>
React Native visionOS allows you to write visionOS with full
support for platform SDK.
</Text>
</View>
</Section>
{/* テキスト入力 */}
<Section title="Text Input">
<View style={{flexDirection: 'row'}}>
<TextInput
style={{paddingVertical: 12, paddingHorizontal: 32}}
onChangeText={() => Alert.alert('Change Text')}
placeholder="Input me"
/>
</View>
</Section>
{/* スイッチ */}
<Section title="Switch">
<View style={{flexDirection: 'row'}}>
<Switch onValueChange={toggleSwitch} value={isEnabled} />
</View>
</Section>
{/* ローディングなどに使えるインジケーター */}
<Section title="ActivityIndicator">
<View style={{flexDirection: 'row'}}>
<ActivityIndicator size={'large'} />
</View>
</Section>
{/* ボタン2種 */}
<Section title="Button">
<View style={{flexDirection: 'row', gap: 10}}>
<Button
title="My Button"
onPress={() => Alert.alert('Simple Button pressed')}
/>
<Pressable
style={{
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
paddingHorizontal: 32,
borderRadius: 4,
elevation: 3,
}}
onPress={() => Alert.alert('Pressable Button pressed')}>
<Text style={{}}>Pressable Button</Text>
</Pressable>
</View>
</Section>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;