Swift中如何独立显示与隐藏多个Overlay遮罩视图
针对你需要多个独立遮罩根据条件动态显示/隐藏、且不影响原有导航栈的需求,我们可以通过给UINavigationController扩展子控制器管理能力来实现,核心是将遮罩ViewController作为导航控制器的子控制器添加到视图层级,而非使用present或push,这样每个遮罩可以独立管控,互不干扰。
核心实现思路
- 给
UINavigationController维护一个字典,存储所有遮罩VC及其唯一标识(用于区分不同条件的遮罩) - 遮罩VC作为导航控制器的子控制器添加,视图层级置于最上层,实现全屏遮罩效果
- 提供添加、移除、更新遮罩的方法,支持根据条件独立控制每个遮罩的显示/隐藏
代码实现
UINavigationController扩展
import UIKit extension UINavigationController { // 存储遮罩VC,key为遮罩的唯一标识(比如"locationPermission"、"networkError") private var overlayVCs: [String: UIViewController] { get { objc_getAssociatedObject(self, &AssociatedKeys.overlayVCs) as? [String: UIViewController] ?? [:] } set { objc_setAssociatedObject(self, &AssociatedKeys.overlayVCs, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } private struct AssociatedKeys { static var overlayVCs = "overlayVCs" } /// 添加遮罩VC /// - Parameters: /// - vc: 遮罩视图控制器 /// - identifier: 唯一标识,用于后续移除/更新 /// - animated: 是否显示动画 func addOverlay(_ vc: UIViewController, identifier: String, animated: Bool = true) { guard overlayVCs[identifier] == nil else { return } // 设置遮罩VC的frame为导航控制器的bounds vc.view.frame = view.bounds vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addChild(vc) view.addSubview(vc.view) vc.didMove(toParent: self) overlayVCs[identifier] = vc if animated { vc.view.alpha = 0 UIView.animate(withDuration: 0.3) { vc.view.alpha = 1 } } } /// 移除指定标识的遮罩VC /// - Parameters: /// - identifier: 遮罩的唯一标识 /// - animated: 是否显示动画 func removeOverlay(with identifier: String, animated: Bool = true) { guard let vc = overlayVCs[identifier] else { return } if animated { UIView.animate(withDuration: 0.3, animations: { vc.view.alpha = 0 }) { _ in vc.willMove(toParent: nil) vc.view.removeFromSuperview() vc.removeFromParent() self.overlayVCs.removeValue(forKey: identifier) } } else { vc.willMove(toParent: nil) vc.view.removeFromSuperview() vc.removeFromParent() overlayVCs.removeValue(forKey: identifier) } } /// 根据条件批量更新遮罩状态 /// - Parameter conditions: 字典,key为遮罩标识,value为是否需要显示(true显示,false隐藏) func updateOverlays(conditions: [String: Bool], animated: Bool = true) { for (identifier, shouldShow) in conditions { if shouldShow { // 这里需要你提前准备好对应标识的遮罩VC,或者在方法里传入创建逻辑 // 示例:假设你有一个方法可以根据标识创建对应的遮罩VC guard let vc = createOverlayVC(for: identifier) else { continue } addOverlay(vc, identifier: identifier, animated: animated) } else { removeOverlay(with: identifier, animated: animated) } } } /// 示例:根据标识创建对应的遮罩VC(你可以根据自己的需求实现) private func createOverlayVC(for identifier: String) -> UIViewController? { switch identifier { case "locationPermission": let vc = LocationPermissionOverlayVC() return vc case "networkError": let vc = NetworkErrorOverlayVC() return vc default: return nil } } }
使用示例
// 在需要检查条件的地方(比如AppDelegate、BaseViewController) let navigationController = self.window?.rootViewController as! UINavigationController // 模拟条件:定位未开启、网络异常 let conditions: [String: Bool] = [ "locationPermission": !CLLocationManager.locationServicesEnabled(), "networkError": !Reachability.isConnectedToNetwork() ] // 更新遮罩状态 navigationController.updateOverlays(conditions: conditions) // 单独移除某一个遮罩(比如用户开启定位后) navigationController.removeOverlay(with: "locationPermission")
方案优势
- 独立管控:每个遮罩通过唯一标识管理,可单独显示/隐藏,不会出现
present覆盖或push栈混乱的问题 - 不影响原有导航栈:遮罩作为子控制器添加,完全不干扰原有的
present/push操作和导航栈结构 - 关注点分离:所有遮罩管理逻辑封装在导航控制器扩展中,业务代码只需关注条件判断和调用方法即可
内容的提问来源于stack exchange,提问作者ambassador
相关产品推荐
相关产品推荐

