Эта страница переведена машинным способом и может содержать ошибки. Читать оригинал на английском

Документация

Платформо-специфичный код

Один бандл NSH работает на iOS, Android, macOS, Windows и Linux (и в Mac Catalyst), а в браузерах — через веб-рантайм. Большей части приложения проверки платформы вообще не нужны: элементы, стили, раскладка и модули ведут себя везде одинаково, и компоненты, которые вы из них собираете, выглядят везде одинаково. Когда что-то действительно специфично для одной платформы, NSH отводит этому чёткое место, чтобы платформенный код оставался заметным и изолированным:

RustTSXДля чего
Builder-методы (.ripple(..), .input_accessory(..))ios={{…}}, android={{…}}, desktop={{…}}, web={{…}}Props, которые есть только на одной платформе.
on_back_press, nsh_core::element::desktopnsh/mobile, nsh/desktopAPI, которые есть только на мобильных платформах или только на десктопах.
os(), idiom(), is_desktop()platform.os, platform.desktop, platform.selectВыбор значений или кода для каждой платформы.
.when(os() == Os::Ios, ..)Классы ios: / android: / web:Стили для каждой платформы.

#Десктоп и мобильные платформы

Каждая платформа относится либо к десктопным, либо к мобильным, по единому правилу. К десктопным относятся macOS, Windows, Linux и браузеры в десктопной идиоме (мышь и широкое окно, веб). К мобильным относятся iOS, Android и браузеры в идиоме телефона или планшета; Mac Catalyst использует бэкенд iOS, поэтому тоже считается мобильным, с props ios={{…}}. Props desktop={{…}}, platform.select({ desktop, mobile }), platform.desktop и platform.idiom === "desktop" подчиняются этому правилу, как и компоненты nsh/base (меню — это попап на десктопах и sheet на мобильных платформах) и, в Rust, is_desktop().

isAvailable из nsh/desktop отвечает на более узкий вопрос: работают ли API окна и меню этого модуля — они работают на macOS, Windows и Linux. Раскладку, плотность и ввод привязывайте к platform.desktop, а вызовы Window и Menu — к isAvailable.

#Props, существующие только на одной платформе

В Rust prop, который есть на одной платформе, — это обычный builder-метод (.ripple(..), .elevation(..) для Android, .input_accessory(..) для iOS, .window_drag(..) для десктопов); остальные платформы его пропускают. В TSX props, существующие только на одной платформе, сгруппированы в объект, названный по ней. Каждый элемент принимает ios, android, desktop (macOS, Windows, Linux и браузеры в десктопной идиоме) и web (браузеры); на других платформах объект игнорируется, а на своей платформе применяются только его ключи. Десктопный браузер применяет и desktop, и 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(…)
})

Объекты TSX реактивны, как и любой prop; ключ, удалённый из объекта, сбрасывается.

Что есть сегодня:

RustTSXГдеОписание
.ripple(Ripple { .. })android={{ ripple }}каждый элементMaterial-ripple при нажатии: color, borderless, radius, foreground.
.elevation(..)android={{ elevation }}каждый элементMaterial-тень в пунктах, по форме view.
android={{ accessibilityLiveRegion }}каждый элемент"none", "polite" или "assertive": TalkBack озвучивает изменения view.
.status_bar_translucent(..)android={{ statusBarTranslucent }}DialogСодержимое диалога заходит под строку состояния.
.input_accessory(..)ios={{ inputAccessoryViewID }}InputInputAccessoryView, показываемый над клавиатурой.
.auto_fill(..)ios={{ textContentType }}InputНазвания типов автозаполнения в iOS (AutoFill в Rust охватывает обе платформы).
.supported_orientations(..)ios={{ supportedOrientations }}DialogДопустимые ориентации.
.back_forward_gestures(..)ios={{ allowsBackForwardNavigationGestures }}WebViewСвайп назад и вперёд по истории страниц.
.header_right_system_item(..)ios={{ headerRightSystemItem }}NavPageСобственная bar-кнопка UIKit в конце заголовка; по нажатию вызывается headerRight.
.header_left_system_item(..)ios={{ headerLeftSystemItem }}NavPageСобственная bar-кнопка UIKit в начале заголовка; по нажатию вызывается headerLeft.
.header_right_symbol(..)ios={{ headerRightSymbol }}NavPageСобственная bar-кнопка UIKit с SF Symbol в конце заголовка; по нажатию вызывается headerRight.
.header_left_symbol(..)ios={{ headerLeftSymbol }}NavPageСобственная bar-кнопка UIKit с SF Symbol в начале заголовка; по нажатию вызывается headerLeft.
.back_forward_gestures(..)desktop={{ allowsBackForwardNavigationGestures }}WebViewmacOS: свайпы двумя пальцами по трекпаду переходят назад и вперёд по истории страниц; Windows: свайпы по сенсорному экрану; Linux: свайпы по тачпаду.
.window_drag(..)desktop={{ windowDrag }}каждый элементНажатие на него перетаскивает окно, как это делала бы системная строка заголовка (см. nsh/desktop).

В TSX desktop={{…}} и web={{…}} принимаются везде. Типы (IOSProps, AndroidProps, DesktopProps и их формы для отдельных элементов) подсказывают редактору, что принимает каждый элемент.

То, что элемент означает на каждой платформе, — это prop верхнего уровня (в Rust — метод с тем же именем), применяемый там, где он имеет смысл: title, headerStyle, headerLargeTitle и headerBackTitle у NavPage настраивают системный заголовок iOS и Android, его слот header — это заголовок десктопов и веба, а presentation у Dialog выбирает sheet’ы iOS. closeOnBackdropPress, pagingEnabled, refreshing, hitSlop и остальные у AlertDialog.show работают на каждой платформе. Собственная системная возможность платформы помещается в её объект, а если она есть на нескольких платформах — в каждый из них под одним и тем же именем: allowsBackForwardNavigationGestures у WebView, системные свайпы по истории страниц, есть и в ios={{…}}, и в desktop={{…}}.

#Платформенные модули

В Rust API, которые есть только на одной платформе, — это функции nsh_core::prelude (on_back_press, input_accessory_view) и nsh_core::element::desktop (окно: set_title, set_title_bar, title_bar, minimize, toggle_maximize, toggle_full_screen, set_frame, set_level, order_front, close_window; меню: set_application_menu, popup_menu); на других платформах они ничего не делают. В TSX nsh/mobile содержит API iOS и Android (некоторые из них — только одной из платформ), а nsh/desktop — API десктопов. Оба модуля можно импортировать на любой платформе, поэтому один бандл работает везде: на других платформах их функции сразу возвращают управление, а компоненты не занимают места. Каждый экспортирует isAvailable, равный true на своих платформах; platform.os позволяет их различить.

#nsh/mobile

isAvailable равен true на iOS (включая Mac Catalyst) и Android. На обеих платформах есть TabBar — системная панель вкладок.

#BackHandler (Android)

RustTSXОписание
on_back_press(listener)BackHandler.on("press", listener)Слушает кнопку «назад» после диалогов и до стеков; верните true, если событие обработано. Слушатель удаляется вместе с компонентом (или эффектом), который его добавил; в TSX вызов также возвращает функцию, останавливающую его раньше.
BackHandler.exitApp()Выходит из приложения.
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
    });
    // …
}

Кнопку «назад» сначала обрабатывает верхний диалог, затем слушатели «назад» (последний добавленный — первым), затем верхний стек навигации, а если никто её не обработал, приложение завершается. См. Навигация.

#InputAccessoryView (iOS)

RustTSXОписание
input_accessory_view(native_id)InputAccessoryViewПанель над клавиатурой для полей ввода, которые указывают её нативный id (.input_accessory(..), ios={{ inputAccessoryViewID }}).

См. Формы и клавиатура.

#nsh/desktop

Приложения на Rust рисуют собственные строки заголовка с помощью nsh_core::element::desktop (Сборка на Rust). Модуль для TSX:

ЭкспортОписание
WindowОкно: setTitle, setTitleBar({ style: "hidden" }) для собственной строки заголовка приложения, minimize, toggleMaximize, toggleFullScreen, close.
titleBarВысота скрытой строки заголовка и место, занимаемое системными элементами управления окном (реактивно).
MenusetApplicationMenu(items) (строка меню с горячими клавишами) и popup(items, at?) (контекстные меню).
isAvailabletrue там, где работают API окна и меню: macOS, Windows и Linux. У десктопных браузеров platform.desktop, и они сохраняют окно и меню браузера.

См. nsh/desktop.

#Строка состояния

На обеих платформах строка состояния принадлежит верхней странице или диалогу, как в UIKit, где её определяет самый верхний view controller: NavPage и Dialog принимают statusBar (в Rust — .status_bar(StatusBarStyle::Light) или StatusBarSetting для остальных полей) с foreground ("auto", "light" для тёмных фонов, "dark" для светлых), hidden и animated. Побеждает последняя показанная страница или диалог, по полям, а когда она исчезает, снова применяется нижележащая. "statusBar" у nsh.json — значение приложения по умолчанию под ними.

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

В приложении, встроенном в нативное, строку состояния на iOS определяет view controller хост-приложения. Приложению, которое объявляет "capabilities" в nsh.json, нужен "statusBar": true, чтобы её изменить.

#platform.os и platform.select

os() в Rust и константа platform из nsh в TSX сообщают, где запущено приложение: Os::Ios, Os::Android, Os::MacOs, Os::Windows, Os::Linux или Os::Web, а platform.os "ios", "android", "macos", "windows", "linux" или "web" (Mac Catalyst — это "ios", с platform.catalyst в любой из его идиом; бэкенд macOS — это "macos", бэкенд Linux — "linux", а бэкенд Windows — "windows", и их элементы применяют props desktop; браузеры — это "web" и тоже применяют props desktop, когда platform.desktop равно true). О версии, идиоме и остальном см. Устройство и окружение.

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

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

#platform.select

В Rust значение выбирает match os(), с is_desktop() для десктопов. В TSX platform.select(spec) возвращает значение для текущей платформы. Он ищет ключ самой ОС, затем desktop или mobile по правилу выше (platform.desktop), затем native (каждая платформа, кроме веба), затем 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",
};

Он возвращает undefined, если ничего не подошло и нет default.

#Отрисовка разных компонентов

Выбирайте компоненты так же, на уровне модуля (TSX), если выбор никогда не меняется:

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

или прямо в разметке с помощью when / <If>:

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

#Платформенные варианты в Tailwind

В Rust стили для платформ задаются через .when(os() == Os::Ios, |v| ..); в TSX варианты ios:, android: и web: применяют классы на одной платформе:

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

#Отдавайте предпочтение общему поведению

Прежде чем прибегать к проверке платформы, подумайте, важна ли эта разница вашим пользователям. Элементы и стили уже соответствуют нативному поведению каждой платформы там, где оно должно различаться (физика прокрутки, отрисовка текста, клавиатуры, доступность), и остаются одинаковыми там, где пользователи ждут единообразия (собственный облик вашего приложения). Проверки платформы лучше оставлять для платформенных соглашений, на которые полагаются пользователи: кнопка «назад» и toast’ы в Android, sheet’ы и большие заголовки в iOS, названия типов автозаполнения, ripple в Android.

#См. также