如何在iPad设备上实现类似iPhone的BottomSheet界面
在iPad上实现类似iPhone的BottomSheet界面的两种方案
针对你的需求,这里提供两种在iPad上实现底部滑出式BottomSheet的方法,适配不同的iOS版本和自定义需求:
方案一:调整原生Sheet的iPad表现(iOS 16+)
默认iPad的原生Sheet是居中弹窗样式,但可以通过配置UISheetPresentationController让它变成底部附着的BottomSheet,和iPhone的表现一致:
struct ContentView: View { @State private var showingSheet = false var body: some View { Button("Show Sheet") { showingSheet.toggle() } .sheet(isPresented: $showingSheet) { Text("This is my app") .presentationDetents([.medium, .large]) .presentationCornerRadius(20) // 添加上部圆角 .presentationDragIndicator(.visible) // 显示顶部拖动条 .onAppear { // 配置iPad的Sheet为底部附着模式 guard let sheetController = UIApplication.shared.windows.first? .rootViewController? .presentedViewController as? UISheetPresentationController else { return } sheetController.prefersEdgeAttachedInCompactHeight = true sheetController.widthFollowsPreferredContentSizeWhenEdgeAttached = true sheetController.preferredCornerRadius = 20 } } } }
说明:
- 通过获取底层的
UISheetPresentationController,强制让Sheet在iPad上从底部弹出,而非居中显示 - 保留原生的停靠位(medium/large)、拖动条和圆角特性,和iPhone的BottomSheet体验一致
方案二:自定义BottomSheet组件(兼容iOS 15及以下)
如果需要支持更早的iOS版本,或者需要更灵活的自定义效果,可以自己实现一个底部弹出视图,完全用SwiftUI手势和布局控制:
// 自定义BottomSheet组件 struct CustomBottomSheet<Content: View>: View { @Binding var isPresented: Bool let content: Content let detents: [CGFloat] // 自定义停靠高度,比如屏幕高度的50%和80% init(isPresented: Binding<Bool>, detents: [CGFloat], @ViewBuilder content: () -> Content) { self._isPresented = isPresented self.detents = detents.sorted() self.content = content() } @State private var offsetY: CGFloat = UIScreen.main.bounds.height var body: some View { ZStack(alignment: .bottom) { if isPresented { // 背景遮罩 Color.black.opacity(0.3) .ignoresSafeArea() .onTapGesture { isPresented = false } .transition(.opacity) // BottomSheet内容 VStack(spacing: 0) { // 顶部拖动指示器 Rectangle() .frame(width: 40, height: 6) .cornerRadius(3) .foregroundColor(.gray) .padding(.top, 8) content } .background(Color.white) .cornerRadius(20, corners: [.topLeft, .topRight]) .offset(y: offsetY) .gesture( DragGesture() .onChanged { value in // 限制拖动范围,不能超过屏幕底部,也不能高于最小停靠位 offsetY = max(offsetY + value.translation.height, detents.first!) } .onEnded { value in // 根据拖动速度和位置自动吸附到最近的停靠位 if value.velocity.height > 500 { isPresented = false } else { let closestDetent = detents.min { abs(offsetY - $0) < abs(offsetY - $1) } ?? detents.last! withAnimation(.spring()) { offsetY = closestDetent } } } ) .transition(.move(edge: .bottom)) } } .onChange(of: isPresented) { newValue in withAnimation(.spring()) { offsetY = newValue ? detents.last! : UIScreen.main.bounds.height } } } } // 辅助扩展:给View添加指定角的圆角 extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { clipShape(RoundedCorner(radius: radius, corners: corners)) } } struct RoundedCorner: Shape { var radius: CGFloat = .infinity var corners: UIRectCorner = .allCorners func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } } // 使用示例 struct ContentView: View { @State private var showingSheet = false var body: some View { Button("Show Sheet") { showingSheet.toggle() } .overlay( CustomBottomSheet( isPresented: $showingSheet, detents: [UIScreen.main.bounds.height * 0.5, UIScreen.main.bounds.height * 0.8] ) { Text("This is my custom bottom sheet") .padding() .frame(maxWidth: .infinity) } ) } }
说明:
- 完全自定义的组件,支持点击背景关闭、拖动调整位置、自动吸附到指定停靠位
- 在iPad上默认就是底部弹出的样式,不需要额外适配
- 可以自由修改背景色、圆角大小、拖动条样式等细节
内容的提问来源于stack exchange,提问作者Rokky
相关产品推荐
相关产品推荐

