From react-native-hifi
Provides iOS-native SwiftUI views like List, Section, Toggle, and Picker in Expo React Native apps for authentic iOS UI, with RNHostView for embedding React Native content.
npx claudepluginhub bidah/react-native-hifi --plugin react-native-hifiThis skill uses the workspace's default tool permissions.
`@expo/ui/swift-ui` provides iOS-native SwiftUI views directly in React Native Expo apps. The API mirrors SwiftUI naming conventions, so iOS developers find it immediately familiar. These components render as true SwiftUI views, not approximations.
Integrates SwiftUI views and modifiers into Expo apps using @expo/ui/swift-ui for iOS-native UI. Guides SDK 55 installation, Host wrapping, RNHostView embedding, and extensions.
Enables SwiftUI Views and modifiers in Expo apps via @expo/ui/swift-ui. Mirrors SwiftUI API; wrap in Host, use RNHostView for RN components; SDK 55 only.
Guides installation and usage of Expo UI SwiftUI for SDK 55, enabling SwiftUI views/modifiers in Expo apps via Host, with RNHostView for React Native embeds.
Share bugs, ideas, or general feedback.
@expo/ui/swift-ui provides iOS-native SwiftUI views directly in React Native Expo apps. The API mirrors SwiftUI naming conventions, so iOS developers find it immediately familiar. These components render as true SwiftUI views, not approximations.
Core principle: Use @expo/ui/swift-ui for iOS-specific screens that must feel fully native. Combine with RNHostView to embed React Native content inside SwiftUI hierarchies when needed.
RNHostView// iOS-specific SwiftUI components
import { List, Section, Label, Toggle, Picker, Gauge } from '@expo/ui/swift-ui';
// Cross-platform @expo/ui components (Switch, Slider, etc.) work on both platforms
import { Switch, Slider } from '@expo/ui';
SwiftUI's List provides native iOS list rendering with section headers, footers, and grouped styles:
import { List, Section, Label, Text } from '@expo/ui/swift-ui';
function SettingsScreen() {
return (
<List style={{ flex: 1 }}>
<Section header="General" footer="Customize your app experience">
<Label
title="Language"
systemImage="globe"
onPress={() => router.push('/language')}
/>
<Label
title="Notifications"
systemImage="bell"
onPress={() => router.push('/notifications')}
/>
</Section>
<Section header="Account">
<Label
title="Profile"
systemImage="person.circle"
onPress={() => router.push('/profile')}
/>
<Label
title="Privacy"
systemImage="lock.shield"
onPress={() => router.push('/privacy')}
/>
</Section>
</List>
);
}
// Inset grouped (default iOS Settings style)
<List listStyle="insetGrouped" style={{ flex: 1 }}>
{/* ... */}
</List>
// Plain list
<List listStyle="plain" style={{ flex: 1 }}>
{/* ... */}
</List>
// Sidebar (iPad)
<List listStyle="sidebar" style={{ flex: 1 }}>
{/* ... */}
</List>
Native iOS toggle with SwiftUI styling:
import { Toggle } from '@expo/ui/swift-ui';
import { useState } from 'react';
function NotificationSettings() {
const [pushEnabled, setPushEnabled] = useState(true);
const [soundEnabled, setSoundEnabled] = useState(false);
return (
<List style={{ flex: 1 }}>
<Section header="Notifications">
<Toggle
value={pushEnabled}
onValueChange={setPushEnabled}
label="Push Notifications"
systemImage="bell.fill"
/>
<Toggle
value={soundEnabled}
onValueChange={setSoundEnabled}
label="Sound"
systemImage="speaker.wave.2"
/>
</Section>
</List>
);
}
Native iOS picker with multiple styles:
import { Picker } from '@expo/ui/swift-ui';
import { useState } from 'react';
function ThemePicker() {
const [theme, setTheme] = useState('system');
return (
<Picker
selectedValue={theme}
onValueChange={setTheme}
label="Theme"
style="menu" // 'menu', 'wheel', 'segmented', 'inline'
>
<Picker.Item label="System" value="system" />
<Picker.Item label="Light" value="light" />
<Picker.Item label="Dark" value="dark" />
</Picker>
);
}
Native iOS gauge for displaying progress or levels:
import { Gauge } from '@expo/ui/swift-ui';
function StorageIndicator({ used, total }) {
return (
<Gauge
value={used / total}
label="Storage"
currentValueLabel={`${used} GB`}
minimumValueLabel="0"
maximumValueLabel={`${total} GB`}
gaugeStyle="accessoryCircular" // 'linearCapacity', 'accessoryCircular', 'accessoryLinear'
/>
);
}
SwiftUI-style label with system image:
import { Label } from '@expo/ui/swift-ui';
<Label
title="Downloads"
systemImage="arrow.down.circle"
tintColor="#007AFF"
/>
<Label
title="Favorites"
subtitle="12 items"
systemImage="heart.fill"
tintColor="#FF3B30"
/>
RNHostView lets you embed React Native components inside a SwiftUI view hierarchy. This is the inverse of using SwiftUI in React Native.
import { RNHostView } from '@expo/ui/swift-ui';
// In your SwiftUI-driven layout
function HybridScreen() {
return (
<List style={{ flex: 1 }}>
<Section header="Native Section">
<Label title="Native Row" systemImage="star" />
</Section>
<Section header="React Native Content">
<RNHostView style={{ height: 200 }}>
{/* This React Native subtree renders inside SwiftUI */}
<View style={{ flex: 1, backgroundColor: '#f0f0f0', borderRadius: 8 }}>
<Text style={{ padding: 16, fontSize: 16 }}>
React Native content embedded in SwiftUI
</Text>
<CustomChart data={chartData} />
</View>
</RNHostView>
</Section>
</List>
);
}
RNHostView children receive a fixed frame from SwiftUIWrap custom SwiftUI views for use in React Native:
import { requireNativeView } from 'expo';
const NativeSwiftUIView = requireNativeView('MySwiftUIView');
function CustomNativeView({ title, onAction }) {
return (
<NativeSwiftUIView
style={{ height: 200 }}
title={title}
onAction={onAction}
/>
);
}
// ios/MySwiftUIView.swift
import ExpoModulesCore
import SwiftUI
class MySwiftUIModule: Module {
public func definition() -> ModuleDefinition {
Name("MySwiftUIView")
View(MySwiftUIView.self) {
Prop("title") { (view, title: String) in
view.title = title
}
Events("onAction")
}
}
}
struct MySwiftUIView: ExpoView {
@State var title: String = ""
let onAction = EventDispatcher()
var body: some View {
VStack {
Text(title)
.font(.headline)
Button("Tap Me") {
onAction(["action": "tapped"])
}
}
}
}
components/
SettingsForm.tsx # Shared logic/default
SettingsForm.ios.tsx # SwiftUI-based iOS version
SettingsForm.android.tsx # Jetpack Compose Android version
// components/SettingsForm.ios.tsx
import { List, Section, Toggle, Picker } from '@expo/ui/swift-ui';
export function SettingsForm({ settings, onUpdate }) {
return (
<List listStyle="insetGrouped" style={{ flex: 1 }}>
<Section header="Appearance">
<Toggle
value={settings.darkMode}
onValueChange={(v) => onUpdate('darkMode', v)}
label="Dark Mode"
systemImage="moon.fill"
/>
<Picker
selectedValue={settings.accentColor}
onValueChange={(v) => onUpdate('accentColor', v)}
label="Accent Color"
style="menu"
>
<Picker.Item label="Blue" value="blue" />
<Picker.Item label="Purple" value="purple" />
<Picker.Item label="Green" value="green" />
</Picker>
</Section>
</List>
);
}
import { Platform } from 'react-native';
function AdaptiveSettings() {
if (Platform.OS === 'ios') {
const { List, Section, Toggle } = require('@expo/ui/swift-ui');
return (
<List listStyle="insetGrouped" style={{ flex: 1 }}>
{/* SwiftUI rendering */}
</List>
);
}
// Cross-platform fallback
return <ScrollView>{/* ... */}</ScrollView>;
}
| Mistake | Fix |
|---|---|
Importing @expo/ui/swift-ui on Android | Guard with Platform.OS or use .ios.tsx file extension |
Applying React Native StyleSheet to SwiftUI components | SwiftUI components use their own props for styling; use listStyle, gaugeStyle, etc. |
Oversizing RNHostView content | Content must fit within the SwiftUI-provided frame; set explicit height |
Not rebuilding after adding @expo/ui | Requires native rebuild: npx expo run:ios or eas build |
| Mixing SwiftUI navigation with Expo Router | Use Expo Router for screen navigation; SwiftUI for in-screen UI only |
| Expecting SwiftUI hot reload | SwiftUI views require a native rebuild when the Swift code changes |
| Task | Pattern |
|---|---|
| Import SwiftUI components | import { ... } from '@expo/ui/swift-ui' |
| Grouped list | <List listStyle="insetGrouped"> |
| Section with header | <Section header="Title"> |
| Toggle | <Toggle value={v} onValueChange={fn} label="Text" /> |
| Picker | <Picker selectedValue={v} onValueChange={fn} style="menu"> |
| System icon label | <Label title="Text" systemImage="icon.name" /> |
| Embed RN in SwiftUI | <RNHostView><ReactNativeContent /></RNHostView> |
| Custom SwiftUI module | Expo Modules API with Swift + SwiftUI |
| Platform guard | Platform.OS === 'ios' or .ios.tsx file |