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

SwiftUI实现类似Find My的Sheet显示在Tab Bar上方且不覆盖

SwiftUI实现Sheet显示在TabBar上方且不覆盖它

SwiftUI默认的.sheet是系统级模态弹窗,会覆盖整个窗口(包括底部的TabBar)。要实现弹窗显示在TabBar上方且保留TabBar可见,你可以采用以下两种纯SwiftUI或结合UIKit的方案:

方案1:纯SwiftUI自定义弹窗

通过自定义弹窗组件,将其放在TabView的上层区域,底部留出TabBar的空间,同时模拟系统Sheet的交互效果。

修改后的完整代码:

import SwiftUI

struct ContentView: View {
    @State private var showSheet = true
    @State private var sheetOffset = CGSize.zero
    // TabBar默认高度,也可通过UIKit动态获取
    private let tabBarHeight: CGFloat = 49
    
    var body: some View {
        GeometryReader { geo in
            ZStack(alignment: .bottom) {
                TabView {
                    HomeView(showSheet: $showSheet)
                        .tabItem {
                            Label("Home", systemImage: "house")
                        }
                    Text("Second Tab")
                        .tabItem {
                            Label("Second", systemImage: "2.circle")
                        }
                }
                
                // 自定义弹窗
                if showSheet {
                    SheetContent()
                        .frame(height: geo.size.height - tabBarHeight)
                        .background(.white)
                        .cornerRadius(16, corners: [.topLeft, .topRight])
                        .offset(y: sheetOffset.height)
                        .gesture(
                            DragGesture()
                                .onChanged { sheetOffset = $0.translation }
                                .onEnded { value in
                                    if value.translation.height > 50 {
                                        showSheet = false
                                    }
                                    sheetOffset = .zero
                                }
                        )
                        .transition(.move(edge: .bottom))
                }
            }
        }
        .animation(.easeInOut, value: showSheet)
    }
}

struct HomeView: View {
    @Binding var showSheet: Bool
    
    var body: some View {
        VStack {
            Button("Show TabView Sheet") {
                showSheet.toggle()
            }
        }
    }
}

struct SheetContent: View {
    var body: some View {
        VStack(spacing: 20) {
            // 顶部拖拽指示器,模拟系统Sheet样式
            RoundedRectangle(cornerRadius: 3)
                .frame(width: 40, height: 6)
                .foregroundColor(.gray)
                .padding(.top, 10)
            
            Text("First Tab Content")
            Text("More Content in the Sheet")
            Spacer()
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

方案说明:

  • 用ZStack将TabView和自定义弹窗分层,弹窗固定在底部,高度为屏幕总高度减去TabBar高度
  • 添加拖拽手势支持下拉关闭弹窗,顶部的指示器还原系统Sheet的交互体验
  • 通过transition和animation实现平滑的显示/隐藏动画

方案2:结合UIKit调整弹窗层级(兼容iOS 15+)

如果需要更贴近系统Sheet的原生行为(比如支持presentationDetents),可以通过UIViewControllerRepresentable包装UIKit的弹窗控制器,指定弹窗的显示区域避开TabBar。

完整代码示例:

import SwiftUI
import UIKit

struct SheetHostingController<Content: View>: UIViewControllerRepresentable {
    var content: Content
    @Binding var isPresented: Bool
    
    func makeUIViewController(context: Context) -> UIHostingController<Content> {
        let controller = UIHostingController(rootView: content)
        controller.modalPresentationStyle = .pageSheet
        
        if let sheet = controller.sheetPresentationController {
            sheet.detents = [.medium(), .large()]
            // 设置弹窗的显示区域,避开TabBar
            sheet.sourceView = context.coordinator.tabBarController?.view
            let tabBarHeight = context.coordinator.tabBarController?.tabBar.frame.height ?? 49
            sheet.sourceRect = CGRect(
                x: 0,
                y: 0,
                width: UIScreen.main.bounds.width,
                height: UIScreen.main.bounds.height - tabBarHeight
            )
        }
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
        if isPresented, uiViewController.presentingViewController == nil {
            context.coordinator.parentViewController?.present(uiViewController, animated: true)
        } else if !isPresented {
            uiViewController.dismiss(animated: true)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject {
        var parent: SheetHostingController
        weak var parentViewController: UIViewController?
        weak var tabBarController: UITabBarController?
        
        init(parent: SheetHostingController) {
            self.parent = parent
            super.init()
            
            DispatchQueue.main.async {
                self.parentViewController = UIApplication.shared.windows.first?.rootViewController
                self.tabBarController = self.parentViewController as? UITabBarController
            }
        }
    }
}

struct ContentView: View {
    @State private var showSheet = true
    
    var body: some View {
        TabView {
            HomeView(showSheet: $showSheet)
                .tabItem {
                    Label("Home", systemImage: "house")
                }
            Text("Second Tab")
                .tabItem {
                    Label("Second", systemImage: "2.circle")
                }
        }
        .background(SheetHostingController(content: SheetContent(), isPresented: $showSheet))
    }
}

struct HomeView: View {
    @Binding var showSheet: Bool
    
    var body: some View {
        VStack {
            Button("Show TabView Sheet") {
                showSheet.toggle()
            }
        }
    }
}

struct SheetContent: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("First Tab Content")
            Text("More Content in the Sheet")
            Spacer()
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

方案说明:

  • 通过UISheetPresentationController设置弹窗的显示边界,确保弹窗底部停在TabBar上方
  • 保留了系统Sheet的detents(中等/大尺寸)等原生特性
  • 利用Coordinator获取当前的TabBarController,动态计算TabBar高度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 17:15:58