You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 19:57:31