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

SwiftUI仿备忘录式底部Sheet组件是否为系统组件咨询

底部Sheet组件的实现方案

原生系统组件(iOS 15+)

SwiftUI从iOS 15开始提供了原生的底部可拖拽Sheet组件,基于UISheetPresentationController封装,用法和你熟悉的.sheet(isPresented:content:)完全适配,还能配置高度、拖拽指示器等系统原生交互效果,不用自己写手势动画就能达到符合预期的流畅表现。

核心用法示例:

struct ContentView: View {
    @State private var showSheet = false
    
    var body: some View {
        Button("打开底部Sheet") {
            showSheet.toggle()
        }
        .sheet(isPresented: $showSheet) {
            Text("底部Sheet内容")
                .padding()
                .presentationDetents([.medium, .large]) // 设置可停留高度:中等/全屏
                .presentationDragIndicator(.visible) // 显示顶部拖拽条
        }
    }
}
  • presentationDetents:支持预设的.medium()(约屏幕一半高度)、.large()(全屏),也可以自定义固定高度(比如.height(300))
  • presentationDragIndicator:控制顶部拖拽指示器的显示/隐藏
  • 系统原生支持拖拽调整高度、边缘手势关闭,动画效果和系统自带组件完全一致。

iOS 15以下兼容方案(自定义实现)

如果需要兼容更低版本,就得自行实现,但可以优化动画效果:

  1. 用GeometryReader获取屏幕高度,确定Sheet的初始位置和拖拽范围
  2. 结合DragGesture和withAnimation处理拖拽动画,手势结束时根据拖拽距离判断是展开还是收起
  3. 用ZStack将Sheet放在底层内容上方,添加半透明背景遮罩

示例代码:

struct CustomBottomSheet: View {
    @Binding var isPresented: Bool
    let content: () -> View
    
    @State private var offsetY: CGFloat = 0
    private let sheetHeight: CGFloat = 300
    
    var body: some View {
        ZStack {
            // 背景遮罩
            if isPresented {
                Color.black.opacity(0.3)
                    .ignoresSafeArea()
                    .onTapGesture {
                        withAnimation(.easeInOut) {
                            isPresented = false
                        }
                    }
            }
            
            // Sheet内容
            VStack {
                // 拖拽指示器
                Rectangle()
                    .frame(width: 40, height: 6)
                    .cornerRadius(3)
                    .foregroundColor(.gray)
                    .padding(.top, 8)
                
                content()
            }
            .frame(height: sheetHeight)
            .background(Color.white)
            .cornerRadius(16, corners: [.topLeft, .topRight])
            .offset(y: isPresented ? offsetY + UIScreen.main.bounds.height - sheetHeight : UIScreen.main.bounds.height)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        // 限制拖拽范围,不能超过初始位置
                        offsetY = max(value.translation.height, 0)
                    }
                    .onEnded { value in
                        withAnimation(.easeInOut) {
                            // 判断拖拽距离,超过三分之一就收起,否则回弹
                            if value.translation.height > sheetHeight / 3 {
                                isPresented = false
                            }
                            offsetY = 0
                        }
                    }
            )
        }
    }
}

// 使用方式
struct ContentView: View {
    @State private var showCustomSheet = false
    
    var body: some View {
        Button("打开自定义底部Sheet") {
            showCustomSheet.toggle()
        }
        .overlay(
            CustomBottomSheet(isPresented: $showCustomSheet) {
                Text("自定义Sheet内容")
                    .padding()
            }
        )
    }
}
  • 用offset(y:)控制Sheet的位置,结合动画让拖拽和收起动作更顺滑
  • 添加背景遮罩,点击遮罩可快速关闭Sheet
  • 拖拽指示器提升交互辨识度,手势结束的判断逻辑让动画更符合用户预期

内容的提问来源于stack exchange,提问作者마요Mayo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 03:15:51