UITabBarController中弹框切换标签后背景变黑问题排查与解决
这个现象的核心原因和overCurrentContext的展示逻辑、UITabBarController的视图管理机制直接相关:
overCurrentContext的依赖特性:当你用这个模态样式时,弹窗(pVC)会直接展示在发起present操作的原视图控制器的视图上下文之上——简单说,弹窗的“背景”其实是原VC的view。但UITabBarController有个默认行为:切换标签时,会将未选中的视图控制器的view从整个视图层级中移除(目的是节省内存、优化渲染性能)。视图层级断裂:当你切换到其他标签再返回时,原VC的view已经被UITabBarController从层级里删掉了,弹窗失去了它依赖的背景载体,只能显示窗口(UIWindow)的默认黑色背景,所以就出现了“弹窗保留但背景变黑”的情况。
根据你的需求场景,这里提供三种实用的解决方案:
方案一:让弹窗依附于UITabBarController(或根控制器)
既然原VC的view会被移除,那我们可以换个“不会被移除的容器”来present弹窗——比如UITabBarController本身,或者APP的根控制器。这样弹窗的背景会依托于始终存在的tabBarController的view,切换标签也不会断裂。
示例代码(Swift):
// 原VC中按钮的点击事件 @IBAction func showPopupButtonTapped(_ sender: UIButton) { let pVC = PopupViewController() pVC.modalPresentationStyle = .overCurrentContext // 用tabBarController来present,而不是当前VC self.tabBarController?.present(pVC, animated: true) }
如果你的APP根控制器是UINavigationController,也可以用UIApplication.shared.windows.first?.rootViewController来获取根控制器进行present。
方案二:阻止UITabBarController移除未选中VC的view
我们可以自定义UITabBarController,修改它的默认行为——把“移除未选中VC的view”改成“隐藏未选中VC的view”,这样所有子VC的view都会保留在视图层级里,只是切换时隐藏/显示,弹窗的背景就不会丢失了。
示例代码(Swift):
class CustomTabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() // 初始化时把所有子VC的view添加到层级,默认隐藏非选中的 guard let viewControllers = viewControllers else { return } for vc in viewControllers { addChild(vc) vc.view.frame = self.view.bounds self.view.addSubview(vc.view) vc.didMove(toParent: self) vc.view.isHidden = vc != selectedViewController } } override func setSelectedViewController(_ selectedViewController: UIViewController?) { super.setSelectedViewController(selectedViewController) // 切换标签时,隐藏其他VC的view,显示选中的 guard let viewControllers = viewControllers, let selectedVC = selectedViewController else { return } for vc in viewControllers { vc.view.isHidden = vc != selectedVC } } }
之后把你的UITabBarController替换成这个自定义类即可。
方案三:给弹窗添加独立的自定义背景
如果不想修改present的对象或tabBarController的行为,可以给弹窗(pVC)添加一个独立的背景视图,彻底摆脱对原VC view的依赖。比如添加半透明黑色背景,或者在present前把原VC的view快照传递给弹窗作为背景。
示例代码(Swift):
class PopupViewController: UIViewController { // 可选:接收原VC的视图快照 var backgroundSnapshot: UIView? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .clear // 方式1:添加半透明黑色背景 let bgView = UIView(frame: view.bounds) bgView.backgroundColor = UIColor.black.withAlphaComponent(0.6) view.insertSubview(bgView, at: 0) // 方式2:如果要显示原VC的快照,取消下面注释 // if let snapshot = backgroundSnapshot { // snapshot.frame = view.bounds // view.insertSubview(snapshot, at: 0) // } } } // 原VC中传递快照的代码 @IBAction func showPopupButtonTapped(_ sender: UIButton) { let pVC = PopupViewController() pVC.modalPresentationStyle = .overCurrentContext // 生成当前VC的视图快照 pVC.backgroundSnapshot = view.snapshotView(afterScreenUpdates: true) present(pVC, animated: true) }
- 如果希望弹窗切换标签后,背景自动切换为当前显示的VC内容,方案一是最优解;
- 如果弹窗必须始终关联原VC的背景,方案二或方案三更合适;
- 方案三的灵活性最高,适合需要自定义背景样式的场景。
内容的提问来源于stack exchange,提问作者Tigran Iskandaryan

