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

SwiftUI如何在NavigationTitle及Toolbar之上叠加带镂空的View?

SwiftUI实现全局覆盖引导视图(含导航栏/工具栏)及镂空效果

一、实现全局覆盖(突破导航栏/工具栏层级)

SwiftUI的NavigationStack、Toolbar属于视图内部层级,普通overlay无法覆盖。需要直接操作UIWindow添加全局视图,才能让引导层置于所有UI元素之上。

1. 全局引导管理器

用单例管理引导的显示/隐藏,统一操作窗口视图:

class GuideManager: ObservableObject {
    static let shared = GuideManager()
    @Published var isShowingGuide = false
    
    func showGuide(targetFrame: CGRect) {
        isShowingGuide = true
        guard let window = UIApplication.shared.windows.first else { return }
        // 创建并添加引导视图到窗口
        let guideView = GuideOverlayView(frame: window.bounds, targetFrame: targetFrame)
        window.addSubview(guideView)
    }
    
    func hideGuide() {
        guard let window = UIApplication.shared.windows.first else { return }
        window.subviews.first(where: { $0 is GuideOverlayView })?.removeFromSuperview()
        isShowingGuide = false
        // 标记已展示过引导,避免重复弹出
        UserDefaults.standard.set(true, forKey: "hasShownGuide")
    }
}

二、实现镂空效果

通过UIKit的路径填充规则,在半透明遮罩上挖出对应控件的镂空区域,透出下方内容。

1. 带镂空的引导视图

class GuideOverlayView: UIView {
    private let targetFrame: CGRect // 镂空区域的frame(对应需要引导的控件位置)
    
    init(frame: CGRect, targetFrame: CGRect) {
        self.targetFrame = targetFrame
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        backgroundColor = UIColor.black.withAlphaComponent(0.7)
        isUserInteractionEnabled = true
        
        // 添加点击手势关闭引导
        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissGuide))
        addGestureRecognizer(tap)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        // 绘制遮罩路径:全屏区域减去镂空区域
        let fullPath = UIBezierPath(rect: rect)
        let holePath = UIBezierPath(roundedRect: targetFrame, cornerRadius: 8)
        fullPath.append(holePath)
        fullPath.usesEvenOddFillRule = true
        
        // 填充半透明黑色
        UIColor.black.withAlphaComponent(0.7).setFill()
        fullPath.fill()
    }
    
    @objc private func dismissGuide() {
        GuideManager.shared.hideGuide()
    }
}

三、在SwiftUI页面中调用

struct HomeView: View {
    @StateObject private var guideManager = GuideManager.shared
    
    var body: some View {
        NavigationStack {
            Text("主内容区域")
                .navigationTitle("首页")
                .toolbar {
                    Button("设置") {}
                        .background(
                            // 用背景视图获取按钮的全局frame
                            GeometryReader { geo in
                                Color.clear.onAppear {
                                    let globalFrame = geo.frame(in: .global)
                                    // 首次进入时弹出引导
                                    if !UserDefaults.standard.bool(forKey: "hasShownGuide") {
                                        guideManager.showGuide(targetFrame: globalFrame)
                                    }
                                }
                            }
                        )
                }
        }
    }
}

补充说明

  • 若偏好SwiftUI原生实现镂空,可通过mask+blendMode实现,再将视图包装进UIHostingController添加到窗口,效果一致。
  • 镂空区域的targetFrame需通过GeometryReader获取控件的全局坐标,确保位置准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 16:20:49