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

在UINavigationController中使用sheetPresentationController时Sheet层级异常问题

解决Sheet在UINavigationController Push后仍显示在顶层的问题

问题描述

我有一个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操作:

  1. 创建自定义容器VC,内部包含你的业务VC(原根VC的逻辑)
  2. 在容器VC中present Sheet,确保present操作不会被导航控制器劫持
  3. 将自定义容器VC设置为UINavigationController的根VC

内容的提问来源于stack exchange,提问作者Hidayet Ozsoy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:59:55