Esta página se tradujo automáticamente y puede contener errores. Leer el original en inglés

Documentación

Código específico de plataforma

Un mismo bundle de NSH se ejecuta en iOS, Android, macOS, Windows y Linux (y en Mac Catalyst), y en los navegadores mediante el web runtime. La mayor parte de una app no necesita ninguna comprobación de plataforma: los elementos, los estilos, el layout y los módulos se comportan igual en todas partes, y los componentes que construyes con ellos se ven igual en todas partes. Cuando algo realmente es específico de una plataforma, NSH le da un lugar claro, de modo que el código de plataforma sea visible y quede acotado:

Rust TSX Para
Métodos builder (.ripple(..), .input_accessory(..)) ios={{…}}, android={{…}}, desktop={{…}}, web={{…}} Props que solo tiene una plataforma.
on_back_press, nsh_core::element::desktop nsh/mobile, nsh/desktop APIs que solo tienen las plataformas móviles o solo los escritorios.
os(), idiom(), is_desktop() platform.os, platform.desktop, platform.select Elegir valores o código según la plataforma.
.when(os() == Os::Ios, ..) clases ios: / android: / web: Estilos por plataforma.

#Escritorio y móvil

Toda plataforma es de escritorio o móvil, según una única regla. Escritorio es macOS, Windows, Linux y los navegadores en el idioma de escritorio (un ratón y una ventana ancha, la web). Móvil es iOS, Android y los navegadores en el idioma de teléfono o tableta; Mac Catalyst ejecuta el backend de iOS, así que también es móvil, con props ios={{…}}. Las props desktop={{…}}, platform.select({ desktop, mobile }), platform.desktop y platform.idiom === "desktop" siguen todas esta regla, igual que los componentes de nsh/base (un menú es un popup en escritorio y una hoja en móvil) y, en Rust, is_desktop().

El isAvailable de nsh/desktop responde a una pregunta más acotada: si las APIs de ventana y menú de ese módulo funcionan, lo cual ocurre en macOS, Windows y Linux. Condiciona el layout, la densidad y la entrada con platform.desktop, y las llamadas a Window y Menu con isAvailable.

#Props exclusivas de una plataforma

En Rust, una prop que solo tiene una plataforma es un método builder como cualquier otro (.ripple(..), .elevation(..) para Android, .input_accessory(..) para iOS, .window_drag(..) para los escritorios); las demás plataformas la ignoran. En TSX, las props que existen en una sola plataforma se agrupan en un objeto que lleva su nombre. Cada elemento acepta ios, android, desktop (macOS, Windows, Linux y los navegadores en el idioma de escritorio) y web (navegadores); en las demás plataformas el objeto se ignora, y en su propia plataforma solo se aplican sus claves. Un navegador de escritorio aplica tanto desktop como web.

use nsh_base::dialog;
use nsh_core::{
    Ripple,
    element::{AutoFill, Orientation}, prelude::*,
};

view()
    .on_press(open)
    .active(|s| s.opacity(0.6))
    .ripple(Ripple {
        color: Some(rgba(0x0000001f).into()),
        ..Ripple::default()
    })
    .elevation(px(4.0))
    .p_4()
    .rounded(px(12.0))
    .bg(white())
    .child(text("Open"))

input().placeholder("Code").auto_fill(AutoFill::OneTimeCode)

when(shown, || {
    dialog()
        .supported_orientations([Orientation::Portrait])
        .status_bar_translucent(true)
        .child(…)
})

Los objetos de TSX son reactivos como cualquier prop; una clave que se elimina del objeto se borra.

Lo que existe hoy:

Rust TSX En Descripción
.ripple(Ripple { .. }) android={{ ripple }} todos los elementos El ripple de material mientras se pulsa: color, borderless, radius, foreground.
.elevation(..) android={{ elevation }} todos los elementos La sombra de material, en puntos, a partir de la forma de la vista.
android={{ accessibilityLiveRegion }} todos los elementos "none", "polite" o "assertive": TalkBack anuncia los cambios de la vista.
.status_bar_translucent(..) android={{ statusBarTranslucent }} Dialog El contenido del diálogo llega por debajo de la barra de estado.
.input_accessory(..) ios={{ inputAccessoryViewID }} Input El InputAccessoryView que se muestra sobre el teclado.
.auto_fill(..) ios={{ textContentType }} Input Los nombres de iOS para los tipos de autofill (el AutoFill de Rust cubre ambas plataformas).
.supported_orientations(..) ios={{ supportedOrientations }} Dialog Las orientaciones que permite.
.back_forward_gestures(..) ios={{ allowsBackForwardNavigationGestures }} WebView Deslizar hacia atrás y hacia adelante en el historial de páginas.
.header_right_system_item(..) ios={{ headerRightSystemItem }} NavPage El botón de barra propio de UIKit al final de la cabecera, que al pulsarlo ejecuta headerRight.
.header_left_system_item(..) ios={{ headerLeftSystemItem }} NavPage El botón de barra propio de UIKit al inicio de la cabecera, que al pulsarlo ejecuta headerLeft.
.header_right_symbol(..) ios={{ headerRightSymbol }} NavPage El botón de barra propio de UIKit que muestra un SF Symbol al final de la cabecera, que al pulsarlo ejecuta headerRight.
.header_left_symbol(..) ios={{ headerLeftSymbol }} NavPage El botón de barra propio de UIKit que muestra un SF Symbol al inicio de la cabecera, que al pulsarlo ejecuta headerLeft.
.back_forward_gestures(..) desktop={{ allowsBackForwardNavigationGestures }} WebView macOS: los deslizamientos de dos dedos en el trackpad avanzan y retroceden en el historial de páginas; Windows: deslizamientos en pantalla táctil; Linux: deslizamientos en el touchpad.
.window_drag(..) desktop={{ windowDrag }} todos los elementos Al pulsarlo arrastra la ventana, como lo haría la barra de título del sistema (consulta nsh/desktop).

En TSX, desktop={{…}} y web={{…}} se aceptan en todas partes. Los tipos (IOSProps, AndroidProps, DesktopProps y sus formas por elemento) indican a tu editor qué acepta cada elemento.

Lo que un elemento significa en todas las plataformas es una prop de nivel superior (en Rust, un método con el mismo nombre), que se aplica allí donde tiene sentido: title, headerStyle, headerLargeTitle y headerBackTitle de NavPage configuran la cabecera del sistema de iOS y Android, su slot header es la cabecera de los escritorios y la web, y presentation de Dialog elige las hojas de iOS. closeOnBackdropPress, pagingEnabled, refreshing, hitSlop y el resto de AlertDialog.show funcionan en todas las plataformas. Una función propia del sistema de una plataforma va en su objeto y, cuando varias plataformas la tienen, en el de cada una con el mismo nombre: allowsBackForwardNavigationGestures de WebView, los deslizamientos del sistema por el historial de páginas, está en ios={{…}} y desktop={{…}}.

#Módulos de plataforma

En Rust, las APIs que solo tiene una plataforma son funciones de nsh_core::prelude (on_back_press, input_accessory_view) y de nsh_core::element::desktop (la ventana: set_title, set_title_bar, title_bar, minimize, toggle_maximize, toggle_full_screen, set_frame, set_level, order_front, close_window; menús: set_application_menu, popup_menu); en el resto de plataformas no hacen nada. En TSX, nsh/mobile contiene las APIs de iOS y Android (algunas de una sola plataforma) y nsh/desktop las de los escritorios. Ambos se pueden importar en todas las plataformas, de modo que un solo bundle se ejecuta en todas partes: en las demás plataformas sus funciones retornan de inmediato y sus componentes no ocupan espacio. Cada uno exporta isAvailable, que es true en sus propias plataformas; platform.os permite distinguirlos.

#nsh/mobile

isAvailable es true en iOS (con Mac Catalyst) y Android. Ambas plataformas tienen TabBar, la barra de pestañas del sistema.

#BackHandler (Android)

Rust TSX Descripción
on_back_press(listener) BackHandler.on("press", listener) Escucha el botón Atrás después de los diálogos y antes de las pilas; devuelve true cuando lo has gestionado. El listener desaparece con el componente (o efecto) que lo añadió; en TSX la llamada también devuelve una función que lo detiene antes.
BackHandler.exitApp() Sale de la app.
use nsh_core::prelude::*;

fn player() -> impl IntoElement {
    let full_screen = signal(false);
    // Back leaves full screen before it leaves the page. The listener goes
    // with the view.
    on_back_press(move || {
        if !full_screen.get_untracked() {
            return false;
        }
        full_screen.set(false);
        true
    });
    // …
}

El botón Atrás lo gestiona primero el diálogo superior, después los listeners de retroceso (el más reciente primero), luego la pila de navegación superior, y la app se cierra cuando nadie lo ha gestionado. Consulta Navigation.

#InputAccessoryView (iOS)

Rust TSX Descripción
input_accessory_view(native_id) InputAccessoryView Una barra sobre el teclado de los inputs que nombran su id nativo (.input_accessory(..), ios={{ inputAccessoryViewID }}).

Consulta Forms and the keyboard.

#nsh/desktop

Las apps de Rust dibujan sus propias barras de título con nsh_core::element::desktop (Building with Rust). El módulo de TSX:

Exportación Descripción
Window La ventana: setTitle, setTitleBar({ style: "hidden" }) para una barra de título propia de la app, minimize, toggleMaximize, toggleFullScreen, close.
titleBar La altura de la barra de título oculta y el espacio que ocupan los controles de ventana del sistema (reactivo).
Menu setApplicationMenu(items) (la barra de menús, con atajos) y popup(items, at?) (menús contextuales).
isAvailable true donde funcionan las APIs de ventana y menú: macOS, Windows y Linux. Los navegadores de escritorio tienen platform.desktop y conservan la ventana y los menús del navegador.

Consulta nsh/desktop.

#La barra de estado

En ambas plataformas la barra de estado pertenece a la página o al diálogo que está encima, como en UIKit, donde decide el view controller superior: NavPage y Dialog aceptan statusBar (en Rust .status_bar(StatusBarStyle::Light), o un StatusBarSetting para los demás campos) con foreground ("auto", "light" para fondos oscuros, "dark" para fondos claros), hidden y animated. Gana la página o el diálogo mostrado más recientemente, campo por campo, y cuando desaparece vuelve a aplicarse el que está debajo. "statusBar" de nsh.json es el valor predeterminado de la app por debajo de ellos.

use nsh_base::nav_page;
use nsh_core::prelude::*;

fn photo_viewer() -> impl IntoElement {
    nav_page()
        .header_visible(false)
        .status_bar(StatusBarStyle::Light)
        .child(view().flex_1().bg(black()))
}

En una app incrustada en una app nativa, el view controller de la app anfitriona decide la barra de estado en iOS. Una app que declara "capabilities" en nsh.json necesita "statusBar": true para cambiarla.

#platform.os and platform.select

os() en Rust y la constante platform de nsh en TSX indican dónde se ejecuta la app: Os::Ios, Os::Android, Os::MacOs, Os::Windows, Os::Linux o Os::Web, y platform.os "ios", "android", "macos", "windows", "linux" o "web" (Mac Catalyst es "ios", con platform.catalyst en cualquiera de sus dos idiomas; el backend de macOS es "macos", el backend de Linux "linux" y el backend de Windows "windows", y sus elementos aplican sus props desktop; los navegadores son "web", y también aplican las props desktop cuando platform.desktop es true). Consulta Device and environment para la versión, el idioma y el resto.

use nsh_core::{element::Os, prelude::*};

if os() == Os::Android {
    // …
}

#platform.select

En Rust, un match os() elige el valor, con is_desktop() para los escritorios. En TSX, platform.select(spec) devuelve el valor de esta plataforma. Busca la clave propia del SO, luego desktop o mobile según la regla anterior (platform.desktop), luego native (todas las plataformas salvo la web) y por último default:

use nsh_core::{element::Os, prelude::*};

fn card() -> View {
    let card = view().rounded(px(12.0)).bg(white());
    match os() {
        Os::Ios => card.shadow_md(),
        Os::Android => card.border_1().border_color(rgb(0xe5e7eb)),
        _ => card,
    }
}

let hint = match os() {
    Os::Ios => "Swipe down to close",
    Os::Android => "Press back to close",
    _ => "Close",
};

Devuelve undefined cuando nada coincide y no hay default.

#Renderizar componentes distintos

Elige los componentes de la misma manera, a nivel de módulo (TSX) cuando la elección nunca cambia:

fn spinner() -> AnyElement {
    if os() == Os::Ios {
        ios_spinner().into_element()
    } else {
        material_spinner().into_element()
    }
}

o en línea con when / <If>:

when(os() == Os::Ios, || input_accessory_view("bar").child(…))
    .otherwise(|| text("Android has no input accessories."))

#Variantes de plataforma en Tailwind

Rust aplica estilos por plataforma con .when(os() == Os::Ios, |v| ..); en TSX, las variantes ios:, android: y web: aplican clases en una sola plataforma:

view()
    .rounded_xl()
    .bg(white())
    .p_4()
    .when(os() == Os::Ios, |v| v.shadow_md())
    .when(os() == Os::Android, |v| v.border_1().border_color(rgb(0xe4e4e7)))

#Preferir el comportamiento compartido

Antes de recurrir a una comprobación de plataforma, considera si la diferencia importa a tus usuarios. Los elementos y los estilos ya se corresponden con el comportamiento nativo de cada plataforma donde debe diferir (física del scroll, renderizado de texto, teclados, accesibilidad), y se mantienen iguales donde los usuarios esperan coherencia (el aspecto propio de tu app). Lo mejor es reservar las comprobaciones de plataforma para las convenciones en las que los usuarios confían: el botón Atrás y los toasts de Android, las hojas y los títulos grandes de iOS, los nombres de autofill, el ripple de Android.

#Relacionado