iOS 13下UIWindow无法在状态栏上方显示覆盖层的解决方法
在iOS 13中实现状态栏上方的可穿透覆盖层
你的问题核心在于iOS 13引入了UIScene架构,同时ViewController自带的window属性会和你自定义的窗口变量冲突,导致原来的UIWindow初始化方式无法正常关联到当前场景,自然无法显示在状态栏上方。下面是适配后的完整解决方案:
关键修改点说明
- 避免变量名冲突:ViewController本身自带
window只读属性,自定义窗口变量必须重命名,否则会导致iOS 13下窗口关联异常 - 适配UIScene初始化:iOS 13及以后,所有UIWindow必须关联到对应的
UIWindowScene才能正常显示 - 状态栏高度获取兼容:弃用直接调用
UIApplication.shared.statusBarFrame,改用statusBarManager适配iOS 13+的场景架构 - 保持穿透点击逻辑:保留你原来的
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
相关产品推荐
相关产品推荐

