在UINavigationController中使用sheetPresentationController时Sheet层级异常问题
问题描述
我有一个UINavigationController,根视图控制器通过sheetPresentationController弹出Sheet后,当push新的视图控制器,Sheet依然停留在屏幕顶层。调试发现Sheet实际是由导航控制器本身present的,而非根视图控制器,希望Sheet始终限定在根视图控制器的范围内,尝试过不使用子视图控制器实现,但同样无效。
原代码
class ViewController: UIViewController { let sheet = UIViewController() let push = UIViewController() let child = UIViewController() override func viewDidLoad() { super.viewDidLoad() addChild(child) view.addSubview(child.view) child.didMove(toParent: self) let btnPush = UIButton() btnPush.setTitle("PUSH", for: .normal) btnPush.setTitleColor(.black, for: .normal) btnPush.addTarget(self, action: #selector(btnPushPressed), for: .touchUpInside) child.view.addSubview(btnPush) child.view.translatesAutoresizingMaskIntoConstraints = false btnPush.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ child.view.topAnchor.constraint(equalTo: view.topAnchor), child.view.leftAnchor.constraint(equalTo: view.leftAnchor), child.view.rightAnchor.constraint(equalTo: view.rightAnchor), child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), btnPush.centerXAnchor.constraint(equalTo: child.view.centerXAnchor), btnPush.centerYAnchor.constraint(equalTo: child.view.centerYAnchor, constant: -200), ]) sheet.sheetPresentationController?.detents = [.medium()] sheet.sheetPresentationController?.largestUndimmedDetentIdentifier = .medium sheet.view.backgroundColor = .red push.view.backgroundColor = .white child.present(sheet, animated: true) } @objc func btnPushPressed() { navigationController?.pushViewController(push, animated: true) } }
演示效果
点击PUSH按钮后,白色的新视图控制器被push到导航栈,但红色Sheet仍悬浮在屏幕最上层,不会被新视图覆盖。
解决方案
核心问题是UIKit的默认present机制:当从导航栈内的VC发起present时,系统会自动将present操作转移到导航控制器的顶层容器(或窗口的rootViewController),导致Sheet脱离原VC的层级。要让Sheet限定在根VC范围内,需要放弃系统的present流程,将Sheet整合到根VC的视图层级中。
方案1:手动实现Sheet的视图层级嵌入
直接将Sheet作为子控制器添加到根VC,手动模拟弹出动画,这样Sheet会跟随根VC的视图层级,在push新VC时被自然覆盖。
修改后的代码:
class ViewController: UIViewController { let sheet = UIViewController() let push = UIViewController() let child = UIViewController() override func viewDidLoad() { super.viewDidLoad() addChild(child) view.addSubview(child.view) child.didMove(toParent: self) let btnPush = UIButton() btnPush.setTitle("PUSH", for: .normal) btnPush.setTitleColor(.black, for: .normal) btnPush.addTarget(self, action: #selector(btnPushPressed), for: .touchUpInside) child.view.addSubview(btnPush) child.view.translatesAutoresizingMaskIntoConstraints = false btnPush.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ child.view.topAnchor.constraint(equalTo: view.topAnchor), child.view.leftAnchor.constraint(equalTo: view.leftAnchor), child.view.rightAnchor.constraint(equalTo: view.rightAnchor), child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), btnPush.centerXAnchor.constraint(equalTo: child.view.centerXAnchor), btnPush.centerYAnchor.constraint(equalTo: child.view.centerYAnchor, constant: -200), ]) // 配置Sheet样式 sheet.view.backgroundColor = .red sheet.view.layer.cornerRadius = 16 sheet.view.clipsToBounds = true // 将Sheet嵌入根VC的视图层级 addChild(sheet) view.addSubview(sheet.view) sheet.didMove(toParent: self) sheet.view.translatesAutoresizingMaskIntoConstraints = false // 初始状态隐藏在屏幕底部 let bottomConstraint = sheet.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: view.bounds.height * 0.5) NSLayoutConstraint.activate([ sheet.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), sheet.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), sheet.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), bottomConstraint ]) // 执行弹出动画 view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { bottomConstraint.constant = 0 self.view.layoutIfNeeded() } push.view.backgroundColor = .white } @objc func btnPushPressed() { navigationController?.pushViewController(push, animated: true) } }
方案2:自定义容器控制器隔离present操作
如果需要保留UISheetPresentationController的原生能力,可以创建一个自定义容器VC作为导航控制器的根VC,在容器内部管理业务VC和Sheet,避免导航控制器接管present操作:
- 创建自定义容器VC,内部包含你的业务VC(原根VC的逻辑)
- 在容器VC中present Sheet,确保present操作不会被导航控制器劫持
- 将自定义容器VC设置为UINavigationController的根VC
内容的提问来源于stack exchange,提问作者Hidayet Ozsoy
相关产品推荐
相关产品推荐

