水曜日, 8月 6, 2025
水曜日, 8月 6, 2025
- Advertisment -
ホームニューステックニュース期待の新星 Expo UI を触ってみる

期待の新星 Expo UI を触ってみる


はじめに

2025年4月にExpo SDK53がリリースされたのですが、公式が発表したChangelogに興味深い記述がありました。

Improved Swift UI and Jetpack Compose integration, powering a prototype of a new Expo UI package

どうやら、Expoが Swift UI と Jetpac Compose を統合することでネイティブに近いUIコンポーネントを開発者が簡単に使える新たなパッケージ、 Expo UI のプロトタイプを公開したようです。
この Expo UI ですが、Expoでのフロントエンド開発を加速させる期待の新星なのではと注目しています!

https://expo.dev/changelog/sdk-53#improved-swift-ui-and-jetpack-compose-integration-powering-a-prototype-of-a-new-expo-ui-package

https://docs.expo.dev/versions/latest/sdk/ui/

Expo UIが期待の新星である理由

なぜ Expo UI に期待を寄せているのかと言うと、現在のExpoでのフロントエンド開発において必須である「UIライブラリの選定」という面倒な工程を省くことができるのではないかと考えているからです。

Expo UI が有用ライブラリだと…

現状のExpoフロントエンド開発においてボタンやピッカーなどのUIライブラリを使いたい場合、様々な選択肢がある中、開発者自身でライブラリの良し悪しを判断して選定する必要があります。
ですが、もしExpo UI が有用なライブラリであった場合、公式が提供・メンテナンスしている Expo UI を利用することで、ライブラリ選定に悩む必要がなくなるという大きなメリットがあります。

Expo UI の特徴

実際に触っていく前に、Expo UI の特徴について調べてみました。

1. 公式メンテナンスの信頼性

Expoチームが直接メンテナンスをしているため、他のサードパーティ製UIライブラリに比べて以下のような信頼性が担保されています。

  • Expoとの互換性が高い
  • 将来的なアップデート対応もスムーズ
  • バグ対応・セキュリティ面でも安心感⭕️

2. ネイティブ描画による高パフォーマンス

内部的にはiOSでは SwiftUI 、Androidでは Jetpack Compose を使ってネイティブUIを描画しているので、従来のReact Nativeコンポーネントよりスムーズかつ高速な動作が期待できます。

3. クロスプラットフォーム対応

Expo UIのコンポーネントはiOSとAndroidで同一のコードで動作するように設計されているため、クロスプラットフォームに対応しているアプリ開発のコスト削減が可能です。

4. Expo Go では使用できない

Expo UI はネイティブコード(SwiftUI や Jetpack Compose)を使っているため、ネイティブコードを含むパッケージを利用できないExpo Goでは使用することができません。
Expo UI を試してみたい方は、Expo Go ではなく、EAS を利用してカスタムビルドを作成する必要があります。

5. 現在はアルファ版のプロトタイプ段階

Expo UI はまだ試験的機能として提供されているため、本番アプリに利用する場合は慎重な検討を行ってください。とはいえ、今後Expo標準UIになる可能性も十分にあるため、今のうちに触っておく価値は高いと考えます。

Expo UI をインストールする

特徴について理解したところで、早速 Expo UI をダウンロードしていきます。

npx expo install @expo/ui

無事にダウンロードができたので、もう一度ビルドを実行します。

開発ビルドのセットアップ方法がわからない方

開発ビルドのExpoプロジェクトの立ち上げ方は過去に記事としてまとめているので、わからない方は参考にしてください。

https://zenn.dev/gemcook/articles/7c8298ef7b8d89

実際に触ってみる

実際に全てのコンポーネントを触ってみようと思います!

BottomSheet

デザイン
シンプルなデザインで、汎用性が高そうです。内部の実装に合わせて高さが変わります。
動きは滑らかで、背景をクリックするかボトムシートを下にスライドすることで閉じることができます。

コード
開閉状態を管理するためにuseStateと併用するケースが多そうです。

import { BottomSheet } from '@expo/ui/swift-ui';
import { useState } from 'react';

const [isOpened, setIsOpened] = useState(false);

>
  View>
    Button title='Open Bottom Sheet' onPress={() => setIsOpened(true)}/>
  /View>

  BottomSheet isOpened={isOpened} onIsOpenedChange={setIsOpened}>
    Text>
      Hello, world!
    /Text>
  /BottomSheet>
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
children any ⭕️ BottomSheet内に表示するコンテンツ。
isOpened boolean ⭕️ BottomSheetが開かれているかどうかを示すブール値。
onIsOpenedChange (isOpened: boolean) => void ⭕️ BottomSheetの開閉状態が変更されたときに呼び出されるコールバック関数。

TextInput

デザイン
keyboardTypeを指定することで、フォーカスを当てた時に開くキーボードの種類を制御することができます。


Basic & Phone & Email


URL & Twitter/Social & Web

改行を無限に許容することと、最大行数を指定して改行を許容することもできます。
また、英語のみのようですが、タイプミスを補完する機能を任意でオフにすることができます。

コード
onFocusonBlurなどのサブスクライブ系のプロパティは無いようです。

import { TextInput } from '@expo/ui/swift-ui';
import { useState } from 'react';

const [value, setValue] = useState('A single line text input');

TextInput autocorrection={false} defaultValue="A single line text input" onChangeText={setValue} />

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
defaultValue string 任意 初期表示されるテキスト(コンポーネントがアンマウントされるまで保持)
onChangeText (value: string) => void ⭕️ 入力テキストが変更されたときに呼ばれる関数
multiline boolean 任意 true にすると複数行の入力が可能になる(改行キーはなし)
numberOfLines number 任意 multiline が有効な場合の表示行数(超えるとスクロール)
keyboardType キーボードタイプ(下記参照) 任意 表示するキーボードの種類(例: 'default', 'numeric', 'email-address' など)
autocorrection boolean 任意 自動補正の有効/無効(デフォルトは true
style StyleProp 任意 ボタンの外側ラッパーである Host` コンポーネントのスタイル
keyboardTypeの値一覧
説明
'default' 標準のキーボード
'email-address' メールアドレス入力向けキーボード(@. を含む)
'numeric' 数字のみのキーボード(整数)
'phone-pad' 電話番号用の数字キーボード
'ascii-capable' ASCII文字のみ入力可能なキーボード
'numbers-and-punctuation' 数字と句読点を含むキーボード
'url' URL入力に適したキーボード(.com/ 含む)
'name-phone-pad' 名前・電話番号入力向けのキーボード
'decimal-pad' 小数点付きの数値入力用キーボード
'twitter' Twitter向け入力キーボード(@# を含む)
'web-search' 検索向けキーボード(検索ボタン付き)
'ascii-capable-number-pad' ASCII準拠の数字パッド(iOSでのみ有効)

Button

デザイン
複数のプロパティを組み合わせることで多彩なボタンを実装できます。

コード
react-native製のボタンコンポーネントとはtitleプロパティで表示する文字を指定しますが、Expo UI はCildrenで表示内容を指定します。

import { Button } from '@expo/ui/swift-ui';

Button
  style={{ flex: 1 }}
  onPress={() => {
    console.log('clicked');
  }}>
  Click me
/Button>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
children string or React.ReactNode ⭕️ ボタンに表示されるテキストまたはノード
onPress () => void 任意 ボタンが押されたときに呼ばれる関数
systemImage string 任意 Material Icons(Android)やSF Symbols(iOS)の名前
role 'default' | 'cancel' | 'destructive' 任意(iOSのみ) iOSでの意味づけ(警告・キャンセルなど)
variant ボタンスタイル(下記参照) 任意 OSごとのビジュアルスタイル
color string(カラーコード) 任意 ボタンの色(redblueでも可)
disabled boolean 任意 true にするとボタンが無効化されて押せなくなる
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル
**variant**の値一覧
値(文字列) 説明 プラットフォーム
'default' デフォルトのシステムボタンスタイル iOS / Android 共通
'bordered' 枠線がある薄い背景のボタン。Android では FilledTonalButton に相当 iOS / Android 共通
'plain' 枠や背景なしのシンプルなボタン iOS / Android 共通
'borderedProminent' 強調された外観の枠線付きボタン(太字・濃い色など) iOS のみ
'borderless' 背景も枠もない透過ボタン(=タップ領域だけ) iOS のみ
'accessoryBar' アクセサリバー(入力補助バー)用のスタイル macOS のみ
'accessoryBarAction' アクセサリバー内での操作ボタンスタイル macOS のみ
'card' カードUIに適したボタンスタイル macOS のみ
'link' リンク風の見た目のボタン macOS のみ

Switch (toggle/checkbox/button)

デザイン
iPhonの設定画面にあるToggleSwitchそのまんまで、動きも非常にスムーズです。
variantプロパティを変えることでCheckboxとButtonにもなります。

コード
現状の公式ページでは開閉のbool値を渡すのはcheckedプロパティと書いていますが、実装はvalueプロパティに渡す設計になっているようです。

import { useState } from 'react';
import { Switch } from '@expo/ui/swift-ui';

const [isChecked, setIsChecked] = useState(false); 

Switch
  value={isChecked}
  onValueChange={setIsChecked} 
  label="通知を許可"
  variant='switch'
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
value boolean ⭕️ スイッチのオン/オフ状態
onValueChange (value: boolean) => void 任意 状態が変更されたときのコールバック
label string 任意 スイッチやボタンの隣や中に表示するラベル
variant 'switch' | 'checkbox' | 'button' 任意 表示形式を指定(省略時はswitch
color string 任意 スイッチがオンのときの色(デフォルトはiOSグリーン)
elementColors undefined 任意 buttonバリアントでは指定できないようになっている
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル)

ColorPicker

デザイン
ネイティブのカラーピッカーそのままなので、使い勝手は非常に良さそうです。

コード
styleプロパティを指定できますが、現状ではwidth,heightを指定しても特に反映されませんでした。

import { useState } from 'react';
import { ColorPicker } from '@expo/ui/swift-ui';

const [color, setColor] = useStatestring | null>('#FF0000'); 

ColorPicker
  style={{ width: 400, height: 200 }}
  selection={color}
  onValueChanged={setColor}
  label='色を選んでください'
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
selection string | null ⭕️ 現在選択されている色。'#RRGGBB' または '#RRGGBBAA' 形式で指定。
label string 任意 カラーピッカーの上に表示されるラベル
onValueChanged (value: string) => void 任意 色が変更されたときに呼ばれるコールバック関数
supportsOpacity boolean 任意 透過度(アルファチャンネル)を選択可能にするかどうか
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル

DateTimePiker (date/time)

デザイン
シンプルながらも幅広い機能性を持っていて、いろんな場面で使えそうです。


DatePicker


TimePicker & DateTimePicker

コード
displayedComponents'date'にするとDatePicker、'time’にするとTimePiker、'dateAndTime'にするとDateTimePickerになります。

import { DateTimePicker } from '@expo/ui/swift-ui';
import { useState } from 'react';

const [selectedDate, setSelectedDate] = useStateDate | null>(null);

DateTimePicker
  initialDate={selectedDate.toISOString()}
  onDateSelected={date => {
    setSelectedDate(date);
  }}
  title='日付を選んでください'
  displayedComponents='date'
  variant='wheel'
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
initialDate string | null 任意 初期表示する日付(例:’2025-01-01’)
title string 任意 iOS でピッカーの上に表示されるタイトル
onDateSelected (date: Date) => void 任意 日付または時間が選択された時に呼ばれるコールバック
variant 'wheel' | 'automatic' | 'graphical' | 'compact' 任意(デフォルト 'automatic' ピッカーの表示スタイル(iOS)
displayedComponents 'date' | 'hourAndMinute' | 'dateAndTime' 任意(デフォルト 'date' ピッカーに表示する項目。日付のみ・時間のみ・両方から選べる
color string 任意 ピッカーのアクセントカラー(iOS用)
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル

Picker

デザイン
個人的にはSegmentedタイプとmenuタイプのピッカーが使いやすそうです。
inlineタイプのピッカーは実装方法がわからなかったので省いています。

コード
現時点だけかもしれませんが、Wheelタイプのピッカーの場合はコンポーネントでラップしないとうまく描画されませんでした。

import React, { useState } from 'react';
import { Picker } from '@expo/ui/swift-ui';

const [selectedIndex, setSelectedIndex] = useStatenumber | null>(0);

Picker
  options={['$', '$$', '$$$', '$$$$']}
  selectedIndex={selectedIndex}
  onOptionSelected={({ nativeEvent: { index } }) => {
    setSelectedIndex(index);
  }}
  variant="segmented"
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
options string[] ⭕️ 選択肢リスト
selectedIndex number | null ⭕️ 現在選択されているインデックス
label string 任意 menu variant 時にフォームに表示されるラベル(iOSのみ)
onOptionSelected (event) => void 任意 選択変更時のコールバック。indexとlabelを取得可能
variant 'wheel' | 'segmented' | 'menu' | 'inline' | 'palette' 任意 表示スタイル(デフォルト: 'segmented'
color string 任意 iOSの menu variant で使用される色

CiclarProgress/LinearProgress

デザイン
どちらも進捗表示系のコンポーネントで、円型と直線型です。
CiclarProgressの方はなぜかローディングスピナーが表示されてしまいますが、、、

コード
使い方は非常に簡単で、どちらも一緒です。

import { CircularProgress, LinearProgress } from '@expo/ui/swift-ui';


CircularProgress progress={0.6} color="blue" />


LinearProgress progress={0.3} color="green" />

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
progress number | null 任意 プログレスの値
color ColorValue 任意 プログレスの色('red''#FF0'など)

Gauge

デザイン
CiclarProgress/LinearProgressと似ていますが、こちらの方は温度や容量の用途に向いていてグラデーションカラーなども実装できます。

コード
colorプロパティに色の配列を渡すとグラデーションになります。

import { Gauge } from "@expo/ui/swift-ui";

Gauge
  max={{ value: 100, label: '100%' }}
  min={{ value: 0, label: '0%' }}
  current={{ value: 50 }}
  color={["blue", "green", "yellow", "orange", "red"]}
  type="circularCapacity"
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
label string 任意 ゲージの上などに表示するテキスト
labelColor ColorValue 任意 ラベルの色
current ValueOptions ⭕️ 現在の値。value(数値)と、必要ならlabel, color を含む
min ValueOptions 任意 最小値の設定(ラベルや色も指定可能)
max ValueOptions 任意 最大値の設定(ラベルや色も指定可能)
type 'default' | 'circular' | 'circularCapacity' | 'linear' | 'linearCapacity' 任意 ゲージのスタイルタイプ(iOS専用)
color ColorValue or ColorValue[] 任意 ゲージの色。グラデーションの場合は配列で指定可能
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル

Slider

デザイン
非常にシンプルなスライダーです。色を指定することができます。

コード
minmaxを指定してない場合は 0 ~ 1 のスライダーになります。

import { Slider } from '@expo/ui/swift-ui';
import { useState } from 'react';

const [value, setValue] = useState(0.3);

Slider
  style={{ minHeight: 60 }}
  value={value}
  onValueChange={(value) => {
    setValue(value);
  }}
/>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
value number 任意 現在のスライダーの値(デフォルト: 0
steps number 任意 ステップ数。0だと無限ステップ(連続的なスライド)(デフォルト: 0
min number 任意 スライダーの最小値(デフォルト: 0
max number 任意 スライダーの最大値(デフォルト: 1
color string 任意 スライダーの色
onValueChange (value: number) => void 任意 値が変化したときに呼ばれるコールバック
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル

List

デザイン
設定画面でよくみるリストに非常に近く、選択できるリストや並び替え・削除ができるリストなどもあります。現時点ではまだアルファ版なので仕方がないのですが、挙動やレンダリングが不安定です。

コード
childrenを渡すだけでも機能しますが、複数のプロパティを組み合わせることも可能です。

import { Text } from 'react-native';
import { List } from '@expo/ui/swift-ui/List'; 

List
  listStyle="insetGrouped"
  deleteEnabled
  onDeleteItem={(index) => console.log("削除:", index)}
>
  Text>機内モード/Text>
  Text>Wi-Fi/Text>
  Text>Bluetooth/Text>
/List>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
children React.ReactNode ⭕️ リスト内に表示するコンテンツ
listStyle "automatic" | "plain" | "inset" | "insetGrouped" | "grouped" | "sidebar" 任意 SwiftUIのListStyleを指定
selectEnabled boolean 任意 項目の選択を有効にする
moveEnabled boolean 任意 項目のドラッグによる並び替えを有効にする
deleteEnabled boolean 任意 項目のスワイプ削除を有効にする
scrollEnabled boolean 任意 リストのスクロールを有効にする(iOS 16+)
editModeEnabled boolean 任意 SwiftUIの編集モードを有効にする
onDeleteItem (index: number) => void 任意 項目が削除されたときのコールバック
onMoveItem (from: number, to: number) => void 任意 項目が並び替えられたときのコールバック
onSelectionChange (selection: number[]) => void 任意 項目の選択状態が変化したときのコールバック
style StyleProp 任意 ボタンの外側ラッパーである Host コンポーネントのスタイル

デザイン
トリガーに指定した要素をタップ/長押しすることでメニューが表示されます。
メニューにはButtonの他に、上記で紹介したSwitchPicker(menu)を埋め込むことも可能です。


Single Press & Long Press


Switch & Picker(close) & Picker(open)

LongPressの場合はプレビューを設定できます。
また、トリガーとプレビューはどちらも写真にすることも可能です。

コード
複数のコンポーネントがあるのでそれぞれご紹介します。

  • ContextMenu

    • メニュー全体を管理するコンポーネント
  • ContextMenu.Trigger

    • メニューを起動するタップ/長押し要素。例えば画像やカードなどの任意のViewを包む
  • ContextMenu.Items

    • Triggerで表示されるメニューアイテム
    • Button, Switch, Picker, Submenuなどの要素をここに並べる
  • ContextMenu.Preview (iOS専用)

    • メニュー起動時に上に表示される内容 (画像のプレビューなど)
import { ContextMenu ,Button } from '@expo/ui/swift-ui';

ContextMenu activationMethod="longPress">
  ContextMenu.Trigger>
    Text>メニューを開くには長押し/Text>
  /ContextMenu.Trigger>

  ContextMenu.Items>
    Button text="menu1" onPress={() => console.log('menu1')} />
    Button text="menu2" onPress={() => console.log('menu2')} />
  /ContextMenu.Items>

  ContextMenu.Preview>
    Image source={source}/>
  /ContextMenu.Preview>
/ContextMenu>

指定できるプロパティは以下の通りです。

プロパティ名 必須 説明
activationMethod "singlePress" | "longPress" 任意 メニューの起動方法。シングルタップまたは長押し。
children ReactNode ⭕️ トリガー要素や コンポーネントなど。
style StyleProp 任意 ボタンの外側ラッパーである Host` コンポーネントのスタイル
  • activationMethod ("singlePress" | "longPress" / 任意)
    メニューの起動方法。シングルタップまたは長押し。

  • children (ReactNode / ⭕️必須)
    トリガー要素や コンポーネントなど。

  • style (StyleProp / 任意)
    ボタンの外側ラッパーである Host コンポーネントのスタイル

まとめ

アルファ版なので不安定なところもありますが、すでに完成度の高いコンポーネントも複数あり、非常に高いポテンシャルを感じました。Expoの公式XによるとExpo SDK 54のリリースは8月末予定のようなので、Expo UIについての発表があるのか注目ですね。

https://x.com/expo/status/1948493075623362616



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -