よりひろい フロントエンド
Author : Kazuhiro Hara
Author : Kazuhiro Hara
Sun Mar 24 2024

VisionOS に React Native の様々な UI コンポーネントをだしてみる

VisionOS のシミュレータで React Native のコンポーネントを表示したところ

さて、一応 React Native で VisionOS 向けのアプリを表示するところまで作れたので、続いてはいろいろなコンポーネントを表示させ、どんな見た目になるか、利用は可能かどうかなどをみていこう

ちなみに react-native-visionos のシリーズは3回目。前回までは以下

ちなみに、改めて第1回目で作ったアプリを起動しようとしたら main.jsbundle がプロジェクトに存在するにもかかわらず同様のエラーが出た。

新しくプロジェクトを作り直したり main.jsbundle を作り直したりしたが、エラー表示は消えない。そこで Xcode プロジェクトに 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 は単なるアラートでも結構かっこいいというのが感想である。

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;
React NativevisionOSXR

Share

About site

カンソクインダストリーズのロゴ

「よりひろいフロントエンド」運営元 カンソクインダストリーズ では、フロントエンドを中心によろずご相談お受けいたします。お気軽にお問い合わせください。