Reference version

BottomSheet

一个 SwiftUI BottomSheet 组件,从屏幕底部展示内容。

iOS
tvOS
Bundled version:
~55.0.0-beta.0

For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

Expo UI BottomSheet 与官方 SwiftUI sheet API 保持一致,并从屏幕底部展示内容。

安装

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.

用法

基础底部弹窗

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="打开弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Text>你好,世界!</Text> </BottomSheet> </VStack> </Host> ); }

自动适配内容的底部弹窗

使用 fitToContents 属性可自动调整弹窗大小以适配其内容。

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="打开弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented} fitToContents> <VStack> <Text>这个弹窗会自动调整大小以适配其内容。</Text> <Button label="关闭" onPress={() => setIsPresented(false)} /> </VStack> </BottomSheet> </VStack> </Host> ); }

带呈现分段的底部弹窗

Group 上使用 presentationDetents 修饰器来控制可用高度。你可以使用:

  • '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="打开弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(['medium', 'large', { fraction: 0.3 }, { height: 200 }]), ]}> <Text>这个弹窗可以在多个高度之间切换。</Text> </Group> </BottomSheet> </VStack> </Host> ); }

带分段选择跟踪的底部弹窗

presentationDetents 传入 selectiononSelectionChange 选项,可通过编程方式控制弹窗切换到哪个分段。

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="显示弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(detents, { selection: selectedDetent, onSelectionChange: setSelectedDetent, }), presentationDragIndicator('visible'), ]}> <List> <Section title="更改分段"> <Button label="高度 300" onPress={() => setSelectedDetent({ height: 300 })} /> <Button label="比例 0.3" onPress={() => setSelectedDetent({ fraction: 0.3 })} /> <Button label="中等" onPress={() => setSelectedDetent('medium')} /> <Button label="" onPress={() => setSelectedDetent('large')} /> </Section> <Section title="当前"> <Text modifiers={[foregroundStyle('secondaryLabel')]}> {formatDetent(selectedDetent)} </Text> </Section> </List> </Group> </BottomSheet> </VStack> </Host> ); }

带背景交互的底部弹窗

使用 presentationBackgroundInteraction 修饰器,允许与弹窗背后的内容交互。

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="打开弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[ presentationDetents(['medium', 'large']), presentationBackgroundInteraction({ type: 'enabledUpThrough', detent: 'medium' }), ]}> <Text>在中等高度时可与背后的内容交互。</Text> </Group> </BottomSheet> </VStack> </Host> ); }

不可手势关闭的底部弹窗

使用 interactiveDismissDisabled 修饰器,防止用户通过上滑关闭弹窗。

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="打开弹窗" onPress={() => setIsPresented(true)} /> <BottomSheet isPresented={isPresented} onIsPresentedChange={setIsPresented}> <Group modifiers={[interactiveDismissDisabled()]}> <VStack> <Text>这个弹窗不能通过滑动关闭。</Text> <Button label="关闭" onPress={() => setIsPresented(false)} /> </VStack> </Group> </BottomSheet> </VStack> </Host> ); }

带 React Native 内容的底部弹窗

使用 RNHostView 在底部弹窗中嵌入 React Native 组件。设置 matchContents 可自动调整宿主视图大小以适配其内容。

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="打开弹窗" 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 内容 </RNText> <RNText style={{ color: '#666', marginBottom: 16 }}>计数器:{counter}</RNText> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, alignItems: 'center', marginBottom: 12, }} onPress={() => setCounter(counter + 1)}> <RNText style={{ color: 'white', fontWeight: '600' }}>增加</RNText> </Pressable> <Pressable style={{ backgroundColor: '#FF3B30', padding: 12, borderRadius: 8, alignItems: 'center', }} onPress={() => setIsPresented(false)}> <RNText style={{ color: 'white', fontWeight: '600' }}>关闭</RNText> </Pressable> </View> </RNHostView> </Group> </BottomSheet> </VStack> </Host> ); }

带灵活 React Native 内容的底部弹窗

当使用带有 flex: 1 的 React Native 内容时,请省略 RNHostView 上的 matchContents 属性,并使用 presentationDetents 来控制弹窗高度。

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="打开弹窗" 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' }}> 灵活的 React Native 内容 </RNText> <RNText style={{ color: 'white', marginTop: 8 }}> 这些内容会填满弹窗中可用的空间。 </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.