このページは機械翻訳のため、誤りを含む場合があります。 英語の原文を読む

API

nsh/mobile

nsh/mobile は iOS と Android 固有の機能を提供します。両プラットフォーム共通のシステムタブバー TabBar、iOS のキーボード上のバー InputAccessoryView、Android の戻るボタン BackHandler です。Rust コードでは nsh_core::mobile と nsh_core::prelude を使い、TSX では nsh/mobile をインポートします。このモジュールはすべてのプラットフォームでインポートできるため、1 つのバンドルをどこでも実行できます。他のプラットフォームでは、コンポーネントは領域を占有せず、呼び出しはすぐに戻ります。isAvailable で iOS か Android かを判定でき、どちらかは nsh の platform.os で分かります。デスクトップ向けの機能は nsh/desktop を参照してください。

use nsh_core::mobile::{TabBarItem, tab_bar};

#スコープ

このモジュールは、アプリのトップレベルのナビゲーションに使うシステムタブバーなど、モバイルプラットフォーム固有の機能を提供します。一般的なコントロールは nsh/base に属します。たとえば Switch は、スマートフォン、デスクトップ、Web で動作と API を共有し、トラック、サム、状態のスタイルはアプリが与えます。TabBar はモバイルアプリのナビゲーション向け、Tabs はプラットフォームをまたぐタブ付きコンテンツ向けです。ここに属するのは、モバイル固有のインタラクションの意味を持つ機能だけです。システム標準の外観、サイズ、アニメーションというだけでは、共通コントロールの共有された役割は変わりません。この区別が、どの機能をここに置くかの指針になります(目標と設計原則)。

#isAvailable

const isAvailable: boolean;

iOS、iOS シミュレーター、Mac Catalyst、Android では true、それ以外では false です。Rust では matches!(os(), Os::Ios | Os::Android) を使います。

#TabBar

タブバーは、記録と統計、受信トレイと設定のように、アプリのトップレベルのセクションを切り替えます。TabBar はシステム自身のバーなので、システムのアプリと同じ見た目と動作になります。iOS と iPadOS では UITabBar(iOS 26 では Liquid Glass とその選択表示)、Android では Material 3 のナビゲーションバーで、ライトとダークのシステムカラーを使います。アプリはタブ(items)を渡し、選択中のタブ(selectedIndex)を指定し、ユーザーの選択(onChange)を受け取り、class と style でバーを配置し、選択中のセクションのコンテンツ自体を表示します。ページ内のタブや、すべてのプラットフォームで使うタブには、nsh/base の Tabs を使用してください。

use nsh_core::{
    mobile::{TabBarItem, tab_bar},
    prelude::*,
};

fn sections() -> impl IntoElement {
    let section = signal(0u32);
    view()
        .flex_1()
        .child(text(move || match section.get() {
            0 => "Records",
            _ => "Stats",
        }))
        .child(
            tab_bar()
                .items(vec![
                    TabBarItem::new("Records").icon("list.bullet"),
                    TabBarItem::new("Stats").icon("chart.bar"),
                ])
                .selected_index(section)
                .on_change(move |index: &u32| section.set(*index))
                .absolute()
                .left_0()
                .right_0()
                .bottom_0(),
        )
}

TabBar は nsh/mobile から取得でき、Rust では nsh_core::mobile です。選択状態はアプリが管理します。タップすると、タブのインデックス({ index })を伴う onChange が通知され、システムのバーと同様に、バーは選択を即座に反映します。アプリはそのセクションを表示し、インデックスを selectedIndex として渡し直します。高さは、スタイルで指定されていなければシステムの値になります。Rust では、tab_bar() でバーを作成します。プロパティごとのセッター(.items(..)、.selected_index(..))、イベントごとのリスナー(.on_change(..))、型付きのスタイルメソッドが用意されています。タブの作成には TabBarItem::new(title).icon(name) を使います。

#プロパティ

プロパティRust型デフォルト説明
items.items(..)TabBarItem[][]タブを順番に指定します。
selectedIndex.selected_index(..)number0選択中のタブをインデックスで指定します。タブの数を超えるインデックスを指定すると、どのタブも選択されません。
hidden.hidden(..)booleanfalseiOS: トランジションなしでバーを非表示にします。この値は push と pop をまたいでアプリが管理します。この prop と、表示中のスタックの hidesBottomBarWhenPushed リクエストの両方が false のときにバーが表示されます。
class、styleスタイルメソッドstring、StyleProp<ViewStyle>なしバーの位置とサイズです。見た目はシステム標準で、選択中のタブにはテーマの primary が使われます。
testID.test_id(..)stringなしテストや nsh test --trace でバーを見つけるために使います。

#TabBarItem

フィールドRust型説明
titleTabBarItem::new(title)stringタブのタイトルです。スクリーンリーダーが読み上げます。
icon.icon(name)stringタブのアイコンです。プラットフォームごとの名前で指定します。iOS では SF Symbol("house")、Android ではアプリの drawable(res/drawable/home.xml は "home"、"android:ic_menu_search" はシステムのもの)です。platform.select でプラットフォームごとに 1 つ選べます。

#イベント

イベントRust説明
onChange.on_change(|index: &u32| ..)ユーザーがタブを選択しました。{ index } はそのインデックスです。バーは選択を即座に反映します。アプリはタブのコンテンツを表示し、selectedIndex を設定します。

#プラットフォーム別の注意

  • iOS と Mac Catalyst: UITabBar で、iOS のバージョンに応じたシステムの外観になります。iOS 26 では、フローティングの Liquid Glass バーとその選択表示です。アイコンは SF Symbols です。テーマの primary がバーの tintColor、つまり選択中のタブの色になります。他のタブはシステムの色のままです。
  • Android: プラットフォームのビューで構成された Material 3 のナビゲーションバーです。各ラベルの上にアクティブインジケーター内のアイコンが表示され、押下時にはリップルが出ます。色は、Android 12 以降ではシステムのダイナミックカラー、それより前では Material のベースラインカラーが使われ、アプリの外観に合わせてライトまたはダークになります。ウィンドウの下端ではナビゲーションバーの下まで広がり、Material と同様にそのインセット分だけ高くなります。アイコンはアプリの drawable です(バーが着色します)。選択中のタブのアイコンとラベルにはテーマの primary が使われ、その 12% の不透明度のアクティブインジケーターが重なります。他のタブはシステムの色のままです。
  • デスクトップ(macOS、Windows、Linux、GPUI)と Web: セクションはアプリの外観の Tabs で表示されます。ここでは TabBar は領域を占有しないため、1 つのレイアウトで両方に対応でき、platform.os(nsh のデバイス API)で切り替えます。

#InputAccessoryView

iOS と Mac Catalyst で動作します。Android では空の非表示ビューです。

function InputAccessoryView(props: InputAccessoryViewProps): JSX.Element;

interface InputAccessoryViewProps {
  nativeID: string;
  style?: StyleProp<ViewStyle>;
  class?: string;
  children?: JSX.Element;
}

キーボードの上に表示されるバー(UIKit の inputAccessoryView)で、nativeID と一致する ios={{ inputAccessoryViewID }} を持つ入力欄にフォーカスがある間、表示されます。Rust では input_accessory_view(native_id) を使い、.input_accessory(native_id) が一致する入力欄に対して表示されます。幅はウィンドウと同じ、高さはコンテンツと同じで、キーボードに合わせてネイティブに移動します。

プロパティ型Rust説明
nativeIDstringinput_accessory_view(native_id)この inputAccessoryViewID を持つ入力欄がバーを表示します。
styleStyleProp<ViewStyle>Styled メソッド(.bg(…))バーのスタイルです(たとえば背景)。
classstringTailwind クラスです。
childrenJSX.Element.child(…)バーのコンテンツです。

ツリー内で配置した場所には何も描画せず、Android ではまったく何も描画しません。そのため、対応する入力欄の隣に置いても問題ありません。

use nsh_core::prelude::*;

#[component]
fn Note() -> impl IntoElement {
    let note = signal(String::new());
    let field = element_ref();
    view()
        .child(
            input()
                .element_ref(&field)
                .value(note)
                .on_change(move |value: &str| note.set(value.to_string()))
                .placeholder("Write a note")
                .input_accessory("note-bar"),
        )
        .child(
            input_accessory_view("note-bar").bg(rgb(0xf1f5f9)).child(
                view()
                    .flex_row()
                    .items_center()
                    .p(px(10.0))
                    .child(
                        text(move || format!("{} characters", note.with(|n| n.chars().count())))
                            .flex_1()
                            .text_color(rgb(0x64748b)),
                    )
                    .child(text("Done").font_weight(FontWeight::SEMIBOLD).on_press(move || field.blur())),
            ),
        )
}

フォームとキーボードも参照してください。

#BackHandler

Android で動作します。iOS ではリスナーは保持されるだけで呼び出されず、exitApp() はすぐに戻ります。

const BackHandler: {
  on(event: "press", listener: () => boolean | void): () => void;
  exitApp(): void;
};

システムの戻るボタンと戻るジェスチャーを扱います。戻る操作はまずダイアログが処理し(ダイアログが閉じます)、次に BackHandler のリスナーが、最後に追加されたものから順に実行されます。どれも処理しなかった場合は最上位のナビゲーションスタックが pop され、戻るページがなければアプリが終了します。

メンバーRust説明
on("press", listener)on_back_press(listener)listener は、操作を処理した場合に true を返します。リスナーの登録を解除する関数を返します。コンポーネントのレンダリング中(またはエフェクトの実行中)に追加されたリスナーは、Rust でも TSX でも、そのコンポーネントとともに停止します。
exitApp()リスナーが false を返すアプリを終了します。Rust のリスナーが false を返すと、操作がそのまま続行され、スタックが戻るか、アプリが終了します。
use nsh_base::alert_dialog;
use nsh_core::prelude::*;

#[component]
fn Editor() -> impl IntoElement {
    let dirty = signal(false);
    on_back_press(move || {
        if !dirty.get_untracked() {
            return false; // let the app go back or exit
        }
        spawn(async move {
            let answer = alert_dialog().title("Discard changes?").cancel("keep", "Keep").destructive("discard", "Discard").show().await;
            if answer.is_ok_and(|r| r.action.as_deref() == Some("discard")) {
                dirty.set(false); // the next back press leaves
            }
        });
        true
    });
    // ...
}

ステータスバーは、iOS でも Android でも、nsh/base のページとダイアログの statusBar で設定します。