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

SwiftUI实现小型底部抽屉UI技术求助

在SwiftUI中实现底部小型抽屉UI的解决方案

问题背景

需要实现从底部弹出的小型抽屉UI,该效果已在UIKit中完成,但SwiftUI下尝试多种方案均遇问题:

  • 使用.sheet搭配系统medium/large detents,无法实现设计要求的更小尺寸弹窗
  • 通过UIViewControllerRepresentable集成UIKit视图时,"Presentation Hosting Controller"始终显示白屏
  • 临时用fullScreenCover,但背景黑屏会随抽屉滑入,点击任意区域即关闭,不符合屏蔽交互+视觉优化的需求

效果差异

当前尝试的问题效果

  • .sheet:弹窗尺寸不符合要求,视图层级存在多余嵌套
  • fullScreenCover:背景层随抽屉滑入,点击任意区域直接关闭,无法满足设计目标

UIKit预期效果

  • 底部弹出小型抽屉,背景半透明层固定显示
  • 无多余视图嵌套,背景层可屏蔽后方交互,且不会直接触发抽屉关闭

解决方案

方案一:纯SwiftUI自定义底部抽屉

直接用SwiftUI的overlay+transition实现,完全可控:

// 自定义抽屉组件
struct CustomBottomDrawer<Content: View>: View {
    @Binding var isPresented: Bool
    let content: Content
    
    init(isPresented: Binding<Bool>, @ViewBuilder content: () -> Content) {
        self._isPresented = isPresented
        self.content = content()
    }
    
    var body: some View {
        ZStack(alignment: .bottom) {
            if isPresented {
                // 背景半透明层,可屏蔽交互
                Color.black.opacity(0.6)
                    .ignoresSafeArea()
                    .onTapGesture {
                        // 可选:点击背景关闭抽屉,不需要则删除此代码
                        isPresented = false
                    }
                    .transition(.opacity)
                
                // 抽屉内容区域
                content
                    .background(Color.white)
                    .cornerRadius(16, corners: [.topLeft, .topRight])
                    .transition(.move(edge: .bottom))
            }
        }
        .animation(.easeInOut(duration: 0.3), value: isPresented)
    }
}

// 扩展:实现指定圆角
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)
    }
}

使用方式

在你的主视图中添加:

@State private var showDrawer = false

var body: some View {
    // 主视图内容...
    EngagementPaymentView(...)
    .overlay(
        CustomBottomDrawer(isPresented: $showDrawer) {
            // 自定义抽屉内容,设置你需要的高度
            VStack(spacing: 16) {
                Text("ABC")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.gray.opacity(0.1))
                // 添加更多按钮或内容
            }
            .frame(height: 150) // 按需设置抽屉高度
        }
    )
}

方案二:修复UIKit视图集成的白屏问题

问题根源是SwiftUI的sheet容器默认背景不透明,且UIKit控制器的modal样式未正确配置,修改如下:

// 修改后的UIKit包装组件
struct ActionSheetSwiftUIView: UIViewControllerRepresentable {
    typealias UIViewControllerType = ActionSheetController
    var actions = [ActionItem]()
    @Binding var isPresented: Bool // 添加绑定用于同步关闭状态
    
    func makeUIViewController(context: Context) -> ActionSheetController {
        let vc = ActionSheetController()
        
        // 关键配置:设置modal样式为全屏覆盖,避免SwiftUI容器干扰
        vc.modalPresentationStyle = .overFullScreen
        vc.modalTransitionStyle = .coverVertical
        
        // 确保背景透明
        vc.view.backgroundColor = .clear
        vc.sheetView.backgroundView.backgroundColor = .clear
        
        // 添加按钮动作
        for action in actions {
            vc.addAction(action)
            vc.addSeparatorView()
        }
        
        // 添加关闭回调(需在ActionSheetController中实现dismissHandler)
        vc.dismissHandler = {
            isPresented = false
        }
        
        return vc
    }
    
    func updateUIViewController(_ uiViewController: ActionSheetController, context: Context) {}
}

使用方式

替换原.sheet代码:

.sheet(isPresented: $isActionSheetSelected) {
    ActionSheetSwiftUIView(
        actions: [ActionItem(buttonStyle: .button(style: .primary, title: "ABC"))],
        isPresented: $isActionSheetSelected
    )
    .ignoresSafeArea()
}

同时需要在ActionSheetController中添加dismissHandler闭包,用于通知SwiftUI关闭状态:

// 在ActionSheetController中添加
var dismissHandler: (() -> Void)?

// 当抽屉需要关闭时调用
// 比如点击按钮或背景时
dismissHandler?()
dismiss(animated: true)

内容的提问来源于stack exchange,提问作者SwiftCoderJr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:45:57