BottomSheet
一个 SwiftUI 底部弹出组件,用于从屏幕底部显示内容。
Expo UI BottomSheet 与官方 SwiftUI sheet API 相匹配,并从屏幕底部渲染内容。
安装
🌐 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 bottom sheet
import { useState } from 'react'; import { Host, BottomSheet, Button, Text, VStack } from '@expo/ui/swift-ui'; export default function BasicBottomSheetExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Text>Hello, world!</Text> </BottomSheet> </VStack> </Host> ); }
适合内容的底部表单
🌐 Bottom sheet that fits content
使用 fitToContents 属性可根据内容自动调整表单大小。
🌐 Use the fitToContents prop to automatically size the sheet to fit its content.
import { useState } from 'react'; import { Host, BottomSheet, Button, Text, VStack } from '@expo/ui/swift-ui'; export default function BottomSheetFitsContentExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented} fitToContents> <VStack> <Text>This sheet automatically sizes to fit its content.</Text> <Button label="Close" onPress={() => setIsPresented(false)} /> </VStack> </BottomSheet> </VStack> </Host> ); }
带展示止点的底部表单
🌐 Bottom sheet with presentation detents
在 Group 上使用 presentationDetents 修饰符来控制可用的高度。你可以使用:
🌐 Use the presentationDetents modifier on Group to control the available heights. You can use:
'medium':系统中等高度(约半屏)'large':系统大高度(全屏){ fraction: number }:屏幕高度的比例(0-1){ height: number }:固定高度(点)
import { useState } from 'react'; import { Host, BottomSheet, Button, Text, VStack } from '@expo/ui/swift-ui'; import { presentationDetents } from '@expo/ui/swift-ui/modifiers'; export default function BottomSheetWithDetentsExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(['medium', 'large', { fraction: 0.3 }, { height: 200 }]), ]}> <Text>This sheet can snap to multiple heights.</Text> </Group> </BottomSheet> </VStack> </Host> ); }
带有止动选择跟踪的底部面板
🌐 Bottom sheet with detent selection tracking
将 selection 和 onSelectionChange 选项传递给 presentationDetents,以编程方式控制工作表停靠到哪个档位。
🌐 Pass selection and onSelectionChange options to presentationDetents to programmatically control which detent the sheet snaps to.
import { useState } from 'react'; import { Host, BottomSheet, Button, List, Section, Text, VStack, Group } from '@expo/ui/swift-ui'; import { presentationDetents, presentationDragIndicator, foregroundStyle, } from '@expo/ui/swift-ui/modifiers'; import type { PresentationDetent } from '@expo/ui/swift-ui/modifiers'; export default function BottomSheetWithDetentSelectionExample() { const [isPresented, setIsPresented] = useState(false); const detents: PresentationDetent[] = [{ height: 300 }, { fraction: 0.3 }, 'medium', 'large']; const [selectedDetent, setSelectedDetent] = useState<PresentationDetent>('medium'); const formatDetent = (detent: PresentationDetent): string => { if (typeof detent === 'string') return detent; if ('fraction' in detent) return `Fraction ${detent.fraction}`; return `Height ${detent.height}`; }; return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Show Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(detents, { selection: selectedDetent, onSelectionChange: setSelectedDetent, }), presentationDragIndicator('visible'), ]}> <List> <Section title="更改档位"> <Button label="Height 300" onPress={() => setSelectedDetent({ height: 300 })} /> <Button label="Fraction 0.3" onPress={() => setSelectedDetent({ fraction: 0.3 })} /> <Button label="Medium" onPress={() => setSelectedDetent('medium')} /> <Button label="Large" onPress={() => setSelectedDetent('large')} /> </Section> <Section title="当前"> <Text modifiers={[foregroundStyle('secondaryLabel')]}> {formatDetent(selectedDetent)} </Text> </Section> </List> </Group> </BottomSheet> </VStack> </Host> ); }
带有背景交互的底部面板
🌐 Bottom sheet with background interaction
使用 presentationBackgroundInteraction 修饰符以允许与工作表后面的内容进行交互。
🌐 Use the presentationBackgroundInteraction modifier to allow interactions with content behind the sheet.
import { useState } from 'react'; import { Host, BottomSheet, Button, Text, VStack } from '@expo/ui/swift-ui'; import { presentationDetents, presentationBackgroundInteraction, } from '@expo/ui/swift-ui/modifiers'; export default function BottomSheetWithBackgroundInteractionExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(['medium', 'large']), presentationBackgroundInteraction({ type: 'enabledUpThrough', detent: 'medium' }), ]}> <Text>Interact with content behind when at medium height.</Text> </Group> </BottomSheet> </VStack> </Host> ); }
不可解除的底部表单
🌐 Non-dismissible bottom sheet
使用 interactiveDismissDisabled 修饰符可以防止用户通过滑动来关闭底部表单。
🌐 Use the interactiveDismissDisabled modifier to prevent users from dismissing the sheet by swiping.
import { useState } from 'react'; import { Host, BottomSheet, Button, Text, VStack } from '@expo/ui/swift-ui'; import { interactiveDismissDisabled } from '@expo/ui/swift-ui/modifiers'; export default function NonDismissibleBottomSheetExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[interactiveDismissDisabled()]}> <VStack> <Text>This sheet cannot be dismissed by swiping.</Text> <Button label="Close" onPress={() => setIsPresented(false)} /> </VStack> </Group> </BottomSheet> </VStack> </Host> ); }
带有 React Native 内容的底部面板
🌐 Bottom sheet with React Native content
使用 RNHostView 在底部弹出层中嵌入 React Native 组件。设置 matchContents 以自动调整宿主视图的大小以适应其内容。
🌐 Use RNHostView to embed React Native components inside the bottom sheet. Set matchContents to automatically size the host view to fit its content.
import { useState } from 'react'; import { Pressable, Text as RNText, View } from 'react-native'; import { Host, BottomSheet, Button, RNHostView, VStack } from '@expo/ui/swift-ui'; import { presentationDragIndicator } from '@expo/ui/swift-ui/modifiers'; export default function BottomSheetWithRNContentExample() { const [isPresented, setIsPresented] = useState(false); const [counter, setCounter] = useState(0); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented} fitToContents> <Group modifiers={[presentationDragIndicator('visible')]}> <RNHostView matchContents> <View style={{ padding: 24 }}> <RNText style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 8 }}> React Native Content </RNText> <RNText style={{ color: '#666', marginBottom: 16 }}>Counter: {counter}</RNText> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, alignItems: 'center', marginBottom: 12, }} onPress={() => setCounter(counter + 1)}> <RNText style={{ color: 'white', fontWeight: '600' }}>Increment</RNText> </Pressable> <Pressable style={{ backgroundColor: '#FF3B30', padding: 12, borderRadius: 8, alignItems: 'center', }} onPress={() => setIsPresented(false)}> <RNText style={{ color: 'white', fontWeight: '600' }}>Close</RNText> </Pressable> </View> </RNHostView> </Group> </BottomSheet> </VStack> </Host> ); }
带有灵活 React Native 内容的底部弹出层
🌐 Bottom sheet with flexible React Native content
在使用带有 flex: 1 的 React Native 内容时,请在 RNHostView 上省略 matchContents 属性,并使用 presentationDetents 来控制面板高度。
🌐 When using React Native content with flex: 1, omit the matchContents prop on RNHostView and use presentationDetents to control the sheet height.
import { useState } from 'react'; import { Text as RNText, View } from 'react-native'; import { Host, BottomSheet, Button, RNHostView, VStack } from '@expo/ui/swift-ui'; import { presentationDetents, presentationDragIndicator } from '@expo/ui/swift-ui/modifiers'; export default function BottomSheetWithFlexRNContentExample() { const [isPresented, setIsPresented] = useState(false); return ( <Host style={{ flex: 1 }}> <VStack> <Button label="Open Sheet" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(['medium', 'large']), presentationDragIndicator('visible'), ]}> <RNHostView> <View style={{ flex: 1, backgroundColor: '#007AFF', padding: 24 }}> <RNText style={{ fontSize: 18, fontWeight: 'bold', color: 'white' }}> Flexible React Native Content </RNText> <RNText style={{ color: 'white', marginTop: 8 }}> This content fills the available space in the sheet. </RNText> </View> </RNHostView> </Group> </BottomSheet> </VStack> </Host> ); }
应用接口
🌐 API
import { BottomSheet } from '@expo/ui/swift-ui';
Component
Type: React.Element<BottomSheetProps>
BottomSheet presents content from the bottom of the screen.
React.ReactNodeThe children of the BottomSheet component.
Use Group to wrap your content and apply presentation modifiers
like presentationDetents, presentationDragIndicator,
presentationBackgroundInteraction, and interactiveDismissDisabled.
boolean • Default: falseWhen true, the sheet will automatically size itself to fit its content.
This sets the presentation detent to match the height of the children.
(isPresented: boolean) => voidCallback function that is called when the BottomSheet presented state changes.