Cette page a été traduite automatiquement et peut contenir des erreurs. Lire l'original en anglais

Documentation

Code spécifique à la plateforme

Un même bundle NSH s’exécute sur iOS, Android, macOS, Windows et Linux (et dans Mac Catalyst), et dans les navigateurs via le runtime web. La plus grande partie d’une application n’a besoin d’aucun test de plateforme : les éléments, les styles, la mise en page et les modules se comportent partout de la même façon, et les composants que vous construisez avec eux ont partout le même aspect. Lorsqu’un besoin est réellement propre à une plateforme, NSH lui donne un emplacement clair, afin que le code spécifique reste visible et circonscrit :

RustTSXPour
Méthodes de builder (.ripple(..), .input_accessory(..))ios={{…}}, android={{…}}, desktop={{…}}, web={{…}}Les props que possède une seule plateforme.
on_back_press, nsh_core::element::desktopnsh/mobile, nsh/desktopLes API propres aux plateformes mobiles ou aux bureaux.
os(), idiom(), is_desktop()platform.os, platform.desktop, platform.selectChoisir des valeurs ou du code selon la plateforme.
.when(os() == Os::Ios, ..)Classes ios: / android: / web:Des styles par plateforme.

#Bureau et mobile

Chaque plateforme est soit un bureau, soit un mobile, selon une règle unique. Les bureaux sont macOS, Windows, Linux, et les navigateurs dans l’idiome bureau (une souris et une fenêtre large, voir le web). Les mobiles sont iOS, Android, et les navigateurs dans l’idiome téléphone ou tablette ; Mac Catalyst utilise le backend iOS, c’est donc aussi un mobile, avec les props ios={{…}}. Les props desktop={{…}}, platform.select({ desktop, mobile }), platform.desktop et platform.idiom === "desktop" suivent toutes cette règle, de même que les composants de nsh/base (un menu est une popup sur les bureaux et une feuille sur mobile) et, en Rust, is_desktop().

Dans nsh/desktop, isAvailable répond à une question plus étroite : savoir si les API de fenêtre et de menu de ce module fonctionnent, ce qui est le cas sur macOS, Windows et Linux. Conditionnez la mise en page, la densité et la saisie à platform.desktop, et les appels de Window et de Menu à isAvailable.

#Props propres à une plateforme

En Rust, une prop que possède une seule plateforme est une méthode de builder comme les autres (.ripple(..), .elevation(..) pour Android, .input_accessory(..) pour iOS, .window_drag(..) pour les bureaux) ; les autres plateformes l’ignorent. En TSX, les props qui n’existent que sur une plateforme sont regroupées dans un objet portant son nom. Chaque élément accepte ios, android, desktop (macOS, Windows, Linux et les navigateurs dans l’idiome bureau) et web (les navigateurs) ; sur les autres plateformes l’objet est ignoré, et sur sa propre plateforme seules ses clés sont appliquées. Un navigateur de bureau applique à la fois desktop et 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(…)
})

Les objets TSX sont réactifs comme n’importe quelle prop ; une clé retirée de l’objet est effacée.

Ce qui existe aujourd’hui :

RustTSXSurDescription
.ripple(Ripple { .. })android={{ ripple }}tout élémentL’effet d’ondulation material pendant l’appui : couleur, sans bordure, rayon, premier plan.
.elevation(..)android={{ elevation }}tout élémentL’ombre material, en points, à partir de la forme de la vue.
android={{ accessibilityLiveRegion }}tout élément"none", "polite" ou "assertive" : TalkBack annonce les changements de la vue.
.status_bar_translucent(..)android={{ statusBarTranslucent }}DialogLe contenu de la boîte de dialogue s’étend sous la barre d’état.
.input_accessory(..)ios={{ inputAccessoryViewID }}InputLe InputAccessoryView affiché au-dessus du clavier.
.auto_fill(..)ios={{ textContentType }}InputLes noms iOS des types de saisie automatique (le AutoFill de Rust couvre les deux plateformes).
.supported_orientations(..)ios={{ supportedOrientations }}DialogLes orientations qu’il autorise.
.back_forward_gestures(..)ios={{ allowsBackForwardNavigationGestures }}WebViewBalayer pour reculer et avancer dans l’historique des pages.
.header_right_system_item(..)ios={{ headerRightSystemItem }}NavPageLe bouton de barre natif d’UIKit à la fin de l’en-tête, qui déclenche headerRight.
.header_left_system_item(..)ios={{ headerLeftSystemItem }}NavPageLe bouton de barre natif d’UIKit au début de l’en-tête, qui déclenche headerLeft.
.header_right_symbol(..)ios={{ headerRightSymbol }}NavPageLe bouton de barre natif d’UIKit affichant un SF Symbol à la fin de l’en-tête, qui déclenche headerRight.
.header_left_symbol(..)ios={{ headerLeftSymbol }}NavPageLe bouton de barre natif d’UIKit affichant un SF Symbol au début de l’en-tête, qui déclenche headerLeft.
.back_forward_gestures(..)desktop={{ allowsBackForwardNavigationGestures }}WebViewmacOS : les balayages à deux doigts sur le trackpad reculent et avancent dans l’historique des pages ; Windows : balayages sur écran tactile ; Linux : balayages sur pavé tactile.
.window_drag(..)desktop={{ windowDrag }}tout élémentUn appui dessus déplace la fenêtre, comme le ferait la barre de titre du système (voir nsh/desktop).

En TSX, desktop={{…}} et web={{…}} sont acceptés partout. Les types (IOSProps, AndroidProps, DesktopProps et leurs formes propres à chaque élément) indiquent à votre éditeur ce que prend chaque élément.

Ce qu’un élément signifie sur toutes les plateformes est une prop de premier niveau (en Rust, une méthode du même nom), appliquée là où elle a un sens : NavPage avec title, headerStyle, headerLargeTitle et headerBackTitle configure l’en-tête système d’iOS et d’Android, son emplacement header est l’en-tête des bureaux et du web, et presentation de Dialog choisit les feuilles d’iOS. closeOnBackdropPress, pagingEnabled, refreshing, hitSlop et les autres de AlertDialog.show fonctionnent sur toutes les plateformes. Une fonctionnalité système propre à une plateforme va dans son objet, et lorsque plusieurs plateformes la possèdent, dans chacun des leurs sous le même nom : allowsBackForwardNavigationGestures de WebView, les balayages système dans l’historique des pages, se trouve dans ios={{…}} et desktop={{…}}.

#Modules de plateforme

En Rust, les API que possède une seule plateforme sont des fonctions de nsh_core::prelude (on_back_press, input_accessory_view) et de nsh_core::element::desktop (la fenêtre : set_title, set_title_bar, title_bar, minimize, toggle_maximize, toggle_full_screen, set_frame, set_level, order_front, close_window ; les menus : set_application_menu, popup_menu) ; ailleurs, elles ne font rien. En TSX, nsh/mobile contient les API d’iOS et d’Android (certaines propres à une seule plateforme), nsh/desktop celles des bureaux. Les deux peuvent être importés sur toutes les plateformes, de sorte qu’un seul bundle s’exécute partout : sur les autres plateformes, leurs fonctions retournent immédiatement et leurs composants ne prennent aucune place. Chacun exporte isAvailable, qui vaut true sur ses propres plateformes ; platform.os permet de les distinguer.

#nsh/mobile

isAvailable vaut true sur iOS (avec Mac Catalyst) et Android. Les deux plateformes disposent de TabBar, la barre d’onglets du système.

#BackHandler (Android)

RustTSXDescription
on_back_press(listener)BackHandler.on("press", listener)Écoute le bouton retour après les boîtes de dialogue, avant les piles ; retournez true lorsqu’il est traité. L’écouteur disparaît avec le composant (ou l’effet) qui l’a ajouté ; en TSX, l’appel retourne aussi une fonction qui l’arrête plus tôt.
BackHandler.exitApp()Quitte l’application.
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
    });
    // …
}

Le bouton retour est traité d’abord par la boîte de dialogue au sommet, puis par les écouteurs de retour (le plus récent en premier), puis par la pile de navigation au sommet, et l’application se ferme si personne ne l’a traité. Voir Navigation.

#InputAccessoryView (iOS)

RustTSXDescription
input_accessory_view(native_id)InputAccessoryViewUne barre au-dessus du clavier des champs de saisie qui désignent son identifiant natif (.input_accessory(..), ios={{ inputAccessoryViewID }}).

Voir Formulaires et clavier.

#nsh/desktop

Les applications Rust dessinent leurs propres barres de titre avec nsh_core::element::desktop (Building with Rust). Le module de TSX :

ExportDescription
WindowLa fenêtre : setTitle, setTitleBar({ style: "hidden" }) pour une barre de titre propre à l’application, minimize, toggleMaximize, toggleFullScreen, close.
titleBarLa hauteur de la barre de titre masquée et la place qu’occupent les contrôles de fenêtre du système (réactif).
MenusetApplicationMenu(items) (la barre de menus, avec raccourcis) et popup(items, at?) (menus contextuels).
isAvailabletrue là où les API de fenêtre et de menu fonctionnent : macOS, Windows et Linux. Les navigateurs de bureau ont platform.desktop et conservent la fenêtre et les menus du navigateur.

Voir nsh/desktop.

#La barre d’état

Sur les deux plateformes, la barre d’état appartient à la page ou à la boîte de dialogue au sommet, comme dans UIKit, où le view controller le plus haut en décide : NavPage et Dialog acceptent statusBar (en Rust .status_bar(StatusBarStyle::Light), ou un StatusBarSetting pour les autres champs) avec foreground ("auto", "light" pour les fonds sombres, "dark" pour les fonds clairs), hidden et animated. La page ou la boîte de dialogue affichée en dernier l’emporte, champ par champ, et celle du dessous s’applique de nouveau lorsqu’elle disparaît. "statusBar" de nsh.json est la valeur par défaut de l’application en dessous d’elles.

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()))
}

Dans une application intégrée à une application native, c’est le view controller de l’application hôte qui décide de la barre d’état sur iOS. Une application qui déclare "capabilities" dans nsh.json a besoin de "statusBar": true pour la modifier.

#platform.os et platform.select

os() en Rust et la constante platform de nsh en TSX indiquent où l’application s’exécute : Os::Ios, Os::Android, Os::MacOs, Os::Windows, Os::Linux ou Os::Web, et platform.os "ios", "android", "macos", "windows", "linux" ou "web" (Mac Catalyst est "ios", avec platform.catalyst dans l’un ou l’autre de ses idiomes ; le backend macOS est "macos", le backend Linux "linux" et le backend Windows "windows", et leurs éléments appliquent leurs props desktop ; les navigateurs sont "web", et appliquent aussi les props desktop lorsque platform.desktop vaut true). Voir Appareil et environnement pour la version, l’idiome et le reste.

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

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

#platform.select

En Rust, un match os() choisit la valeur, avec is_desktop() pour les bureaux. En TSX, platform.select(spec) retourne la valeur correspondant à cette plateforme. Il cherche d’abord la clé propre à l’OS, puis desktop ou mobile selon la règle ci-dessus (platform.desktop), puis native (toutes les plateformes sauf le web), puis 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",
};

Il retourne undefined lorsque rien ne correspond et qu’il n’y a pas de default.

#Afficher des composants différents

Choisissez les composants de la même façon, au niveau du module (TSX) lorsque le choix ne change jamais :

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

ou en ligne avec when / <If> :

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

#Variantes de plateforme dans Tailwind

Rust applique des styles par plateforme avec .when(os() == Os::Ios, |v| ..) ; en TSX, les variantes ios:, android: et web: appliquent des classes sur une seule plateforme :

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)))

#Privilégier le comportement partagé

Avant de recourir à un test de plateforme, demandez-vous si la différence compte pour vos utilisateurs. Les éléments et les styles correspondent déjà au comportement natif de chaque plateforme là où il doit différer (physique du défilement, rendu du texte, claviers, accessibilité), tout en restant identiques là où les utilisateurs attendent de la cohérence (l’apparence propre à votre application). Réservez les tests de plateforme aux conventions de plateforme sur lesquelles les utilisateurs comptent : le bouton retour et les toasts d’Android, les feuilles et les grands titres d’iOS, les noms de saisie automatique, l’ondulation d’Android.

#Voir aussi