4.3 KiB
Platform-specific iOS UI: @expo/ui/swift-ui
iOS only. Code that imports from
@expo/ui/swift-uiwill crash on Android with "Unable to get view config" errors. Always place this code in an.ios.tsxcomponent file or guard it withPlatform.OS === 'ios'.Hostmust be imported from@expo/ui(the universal root), not from@expo/ui/swift-ui.
Use this layer only when the universal @expo/ui components don't cover what you need on iOS (see ./universal.md first). This requires a platform-specific tree.
File placement with Expo Router
Do not put .ios.tsx files inside app/ or src/app/. Expo Router does not support platform-extension suffixes for route files and will throw a "no fallback sibling" Render Error.
Place platform-specific component files in components/ (or any directory outside the route tree), then import them from a regular route file:
src/components/ProfileEditor.ios.tsx ← SwiftUI tree lives here
src/app/profile-editor.tsx ← regular Expo Router route, imports the component
src/app/profile-editor.tsx:
import ProfileEditor from '../components/ProfileEditor';
export default ProfileEditor;
Alternatively, keep everything in one regular route file and branch on Platform.OS:
// src/app/profile-editor.tsx
import { Platform } from 'react-native';
// import SwiftUI components only when on iOS to avoid Android crash
const SwiftUIForm = Platform.OS === 'ios' ? require('../components/ProfileEditor.ios').default : null;
Or more simply, put the Platform.OS guard and the SwiftUI import in the same route file (safe because Metro only bundles .ios.tsx imports on iOS builds when using platform extensions in components/).
Instructions
- Expo UI's API mirrors SwiftUI's API. Use SwiftUI knowledge to decide which components or modifiers to use.
- Components are imported from
@expo/ui/swift-ui, modifiers from@expo/ui/swift-ui/modifiers. - Before writing any code, run the list-components script to get the exact components and modifiers available in the installed version:
(
node <skill-root>/scripts/list-components.js <project-path> # names only (compact) node <skill-root>/scripts/list-components.js <project-path> --docs # with one-line descriptions<skill-root>is the directory containing thisreferences/folder.) - The installed package's TypeScript types (
.d.ts) are the most reliable source of truth for prop shapes and signatures — read the relevant{Component}/index.d.tsfrom the installed@expo/ui/swift-uipackage innode_modules. Use the docs below as the human-readable reference. - When about to use a component, fetch its docs to confirm the API — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/{component-name}/index.md
- When unsure about a modifier's API, refer to the docs — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/modifiers/index.md
- Every SwiftUI tree must be wrapped in
Host. RNHostViewis specifically for embedding RN components inside a SwiftUI tree. Example:
import { Host } from "@expo/ui"; // Host always from universal root
import { VStack, RNHostView } from "@expo/ui/swift-ui"; // platform components from swift-ui
import { Pressable } from "react-native";
<Host matchContents>
<VStack>
<RNHostView matchContents>
// Here, `Pressable` is an RN component so it is wrapped in `RNHostView`.
<Pressable />
</RNHostView>
</VStack>
</Host>;
- If a required modifier or View is missing in Expo UI, it can be extended via a local Expo module. See: https://docs.expo.dev/guides/expo-ui-swift-ui/extending/index.md. Confirm with the user before extending.
useNativeState
useNativeState creates observable state that updates synchronously on the UI thread via worklets, enabling immediate native state changes without waiting for a React render cycle. Requires react-native-worklets — without it updates still go through React and flickering remains. Best for real-time interactions where synchronous updates matter, e.g. a text field that masks or formats input as the user types.
ObservableState.valueis readable/writable from worklets;onChangefires a worklet listener on state change.- Docs — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/usenativestate/index.md