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

iOS 13下UIWindow无法在状态栏上方显示覆盖层的解决方法

在iOS 13中实现状态栏上方的可穿透覆盖层

你的问题核心在于iOS 13引入了UIScene架构,同时ViewController自带的window属性会和你自定义的窗口变量冲突,导致原来的UIWindow初始化方式无法正常关联到当前场景,自然无法显示在状态栏上方。下面是适配后的完整解决方案:

关键修改点说明

  1. 避免变量名冲突:ViewController本身自带window只读属性,自定义窗口变量必须重命名,否则会导致iOS 13下窗口关联异常
  2. 适配UIScene初始化:iOS 13及以后,所有UIWindow必须关联到对应的UIWindowScene才能正常显示
  3. 状态栏高度获取兼容:弃用直接调用UIApplication.shared.statusBarFrame,改用statusBarManager适配iOS 13+的场景架构
  4. 保持穿透点击逻辑:保留你原来的hitTest重写逻辑,确保指定区域可以穿透点击

适配后的完整代码

1. 改进的穿透窗口类

class PassTroughWindow: UIWindow {
    var passTroughTag: Int?
    
    // iOS13+ 专用初始化方法
    init(windowScene: UIWindowScene) {
        super.init(windowScene: windowScene)
        commonSetup()
    }
    
    // iOS12及以下初始化方法
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonSetup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonSetup()
    }
    
    // 统一基础配置
    private func commonSetup() {
        backgroundColor = .clear // 设为透明,避免遮挡下层内容
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        // 如果点击的视图tag匹配,返回nil让点击穿透
        if let passTag = passTroughTag, hitView?.tag == passTag {
            return nil
        }
        return hitView
    }
}

2. 修改后的ViewController

class ViewController: UIViewController {
    // 重命名自定义窗口,避免和系统window属性冲突
    private var statusBarOverlayWindow: PassTroughWindow?
    
    @IBAction func alertButtonPressed(_ sender: UIButton) {
        // 获取状态栏高度,兼容iOS12和iOS13+
        let statusBarHeight: CGFloat
        if #available(iOS 13.0, *) {
            statusBarHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
        } else {
            statusBarHeight = UIApplication.shared.statusBarFrame.height
        }
        
        // 创建状态栏覆盖层
        let bannerFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: statusBarHeight)
        let banner = UIView(frame: bannerFrame)
        banner.layer.cornerRadius = 2.5
        banner.backgroundColor = UIColor(red: 128/255, green: 161/255, blue: 193/255, alpha: 1)
        
        let statusLabel = UILabel(frame: bannerFrame)
        statusLabel.textAlignment = .center
        statusLabel.font = .boldSystemFont(ofSize: 10)
        statusLabel.text = "No internet connection"
        statusLabel.textColor = .black
        banner.addSubview(statusLabel)
        
        // 初始化穿透窗口,适配不同iOS版本
        if let currentScene = view.window?.windowScene {
            statusBarOverlayWindow = PassTroughWindow(windowScene: currentScene)
        } else {
            statusBarOverlayWindow = PassTroughWindow(frame: UIScreen.main.bounds)
        }
        
        guard let overlayWindow = statusBarOverlayWindow else { return }
        
        overlayWindow.rootViewController = UIViewController()
        overlayWindow.windowLevel = .statusBar + 1 // 确保窗口层级在状态栏之上
        overlayWindow.addSubview(banner)
        overlayWindow.makeKeyAndVisible()
        overlayWindow.alpha = 1.0
        
        // 可选:如果需要让覆盖层本身可点击穿透,设置tag并关联
        // banner.tag = 1001
        // overlayWindow.passTroughTag = 1001
    }
}

额外提示

  • 如果需要自动隐藏覆盖层,可以添加定时器,比如3秒后将overlayWindow.alpha设为0并置空,避免内存泄漏
  • 测试时注意在不同设备(刘海屏/非刘海屏)上验证状态栏高度的正确性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:35:53