BottomSheet

一个 SwiftUI 底部弹出组件,用于从屏幕底部显示内容。

iOS
tvOS
Bundled version:
~55.0.2

Expo UI BottomSheet 与官方 SwiftUI sheet API 相匹配,并从屏幕底部渲染内容。

安装

🌐 Installation

Terminal
npx expo install @expo/ui

If you are installing this in an existing React Native app, make sure to install expo in your project.

用法

🌐 Usage

基本底部面板

🌐 Basic bottom sheet

BasicBottomSheetExample.tsx
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.

BottomSheetFitsContentExample.tsx
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 }:固定高度(点)
BottomSheetWithDetentsExample.tsx
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

selectiononSelectionChange 选项传递给 presentationDetents,以编程方式控制工作表停靠到哪个档位。

🌐 Pass selection and onSelectionChange options to presentationDetents to programmatically control which detent the sheet snaps to.

BottomSheetWithDetentSelectionExample.tsx
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.

BottomSheetWithBackgroundInteractionExample.tsx
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.

NonDismissibleBottomSheetExample.tsx
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.

BottomSheetWithRNContentExample.tsx
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.

BottomSheetWithFlexRNContentExample.tsx
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

BottomSheet

iOS
tvOS

Type: React.Element<BottomSheetProps>

BottomSheet presents content from the bottom of the screen.

BottomSheetProps

children

iOS
tvOS
Type: React.ReactNode

The children of the BottomSheet component. Use Group to wrap your content and apply presentation modifiers like presentationDetents, presentationDragIndicator, presentationBackgroundInteraction, and interactiveDismissDisabled.

fitToContents

iOS
tvOS
Optional • Type: boolean • Default: false

When true, the sheet will automatically size itself to fit its content. This sets the presentation detent to match the height of the children.

isPresented

iOS
tvOS
Type: boolean

Whether the BottomSheet is presented.

onIsPresentedChange

iOS
tvOS
Type: (isPresented: boolean) => void

Callback function that is called when the BottomSheet presented state changes.