This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 54).
RNHostView
A component that enables React Native views inside SwiftUI.
A component that enables proper layout behavior when React Native views are rendered inside SwiftUI components. It syncs layout information from SwiftUI back to React Native's Yoga layout system by updating the shadow node size.
When React Native views are placed inside SwiftUI components like BottomSheet, Popover or HStack and so on, the layout systems need to communicate. RNHostView bridges this gap:
- With
matchContents: The shadow node size is set to match the child React Native view's intrinsic size, allowing the SwiftUI parent to size itself based on the React Native content. - Without
matchContents: The shadow node size is set to match the parent SwiftUI view's size, allowing the React Native content to fill the available space (useful forflex: 1layouts).
Installation
- npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
Usage
Basic usage with matchContents
Use matchContents when you want the SwiftUI parent to size itself based on the React Native content.
import { useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { Host, BottomSheet, Button, RNHostView } from '@expo/ui/swift-ui'; function Example() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isOpened={isPresented} onIsOpenedChange={setIsPresented}> <RNHostView matchContents> <View style={{ padding: 24 }}> <Text style={{ fontSize: 18, fontWeight: 'bold' }}>React Native Content</Text> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, marginTop: 16 }} onPress={() => setIsPresented(false)}> <Text style={{ color: 'white', textAlign: 'center' }}>Close</Text> </Pressable> </View> </RNHostView> </BottomSheet> </Host> ); }
Flexible content without matchContents
When using flex: 1 in your React Native content, omit the matchContents prop so the content fills the available SwiftUI space.
import { useState } from 'react'; import { Text, View } from 'react-native'; import { Host, BottomSheet, Button, RNHostView } from '@expo/ui/swift-ui'; function Example() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isOpened={isPresented} onIsOpenedChange={setIsPresented} presentationDetents={['medium', 'large']}> <RNHostView> <View style={{ flex: 1, backgroundColor: '#007AFF', padding: 24 }}> <Text style={{ color: 'white', fontSize: 18 }}> This content fills the available space </Text> </View> </RNHostView> </BottomSheet> </Host> ); }
Usage with Popover
RNHostView works well inside Popover to display interactive React Native content.
import { useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { Host, Button, Popover, RNHostView } from '@expo/ui/swift-ui'; function Example() { const [isPresented, setIsPresented] = useState(false); const [counter, setCounter] = useState(0); return ( <Host style={{ flex: 1 }}> <Popover isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Popover.Trigger> <Button onPress={() => setIsPresented(true)} label="Show Popover" /> </Popover.Trigger> <Popover.Content> <RNHostView matchContents> <View style={{ padding: 24 }}> <Text style={{ fontSize: 16, fontWeight: 'bold', marginBottom: 8 }}> React Native Content </Text> <Text style={{ color: '#666', marginBottom: 12 }}>Counter: {counter}</Text> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, alignItems: 'center', }} onPress={() => setCounter(counter + 1)}> <Text style={{ color: 'white', fontWeight: '600' }}>Increment</Text> </Pressable> </View> </RNHostView> </Popover.Content> </Popover> </Host> ); }
API
Component
Type: React.Element<RNHostViewProps>