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

点击TableView行弹出ViewController时出现视图层级错误求助

解决Attempt to present...whose view is not in the window hierarchy错误

问题根源

你代码里的TutorialsTableView是一个UIViewController子类,但你仅仅实例化了它,既没有把它的视图添加到任何父视图中,也没有通过导航栈或present操作展示它——这意味着它的视图根本不在应用的窗口层级里。当你在didSelectRowAt方法中调用present时,是在这个从未被展示的控制器上执行的,系统自然会抛出那个错误。

而且你完全没必要让table的数据源/代理继承UIViewController,这是设计上的冗余,也是导致问题的核心原因。


解决方案1:让主控制器直接承担数据源和代理职责(最适合新手)

把数据定义、table的数据源/代理方法全部移到TutorialsViewController中,这样所有操作都在已经正确加载到窗口层级的控制器上执行:

class TutorialsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate {

    @IBOutlet weak var tutorialsTable: TableViewAdjustedHeight!

    // 把数据结构移到主控制器内
    struct Tutorials {
        let name: String
        let description: String
    }
    let data: [Tutorials] = [
        Tutorials(name: "ParkProfessorHelp", description: "Park Professor Features"),
        Tutorials(name: "LightningLane", description: "Booking and Modifying Lightning Lane Reservations"),
        Tutorials(name: "MobileOrder", description: "Guide to Mobile Ordering Food"),
        Tutorials(name: "Parking", description: "Parking Tips")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置当前控制器为table的数据源和代理
        tutorialsTable.delegate = self
        tutorialsTable.dataSource = self
    }

    // MARK: - UITableView DataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let tutorials = data[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "tutorialstableCell", for: indexPath) as! tutorialstableCell
        cell.tutorialsImage.image = UIImage(named: tutorials.name + ".png")
        cell.tutorialsLabel.text = tutorials.description
        return cell
    }

    // MARK: - UITableView Delegate
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let tutorials = data[indexPath.row]
        let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        // 增加安全校验,避免找不到控制器崩溃
        guard let popupVC = mainStoryboard.instantiateViewController(withIdentifier: tutorials.name) else {
            print("未找到标识符为\(tutorials.name)的ViewController")
            return
        }
        popupVC.modalPresentationStyle = .popover
        // 用当前主控制器present,它的视图已经在窗口层级中
        present(popupVC, animated: true, completion: nil)
    }
}

class tutorialstableCell: UITableViewCell {
    @IBOutlet weak var tutorialsImage: UIImageView!
    @IBOutlet weak var tutorialsLabel: UILabel!
}

解决方案2:将数据源/代理分离为独立类(代码解耦场景)

如果希望把table的逻辑和主控制器分离,不要让数据源类继承UIViewController,而是让它持有主控制器的弱引用,通过主控制器来present新页面:

class TutorialsViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var tutorialsTable: TableViewAdjustedHeight!
    // 实例化独立的数据源类
    private let tutorialsDataSource = TutorialsTableDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()
        // 将主控制器赋值给数据源类,用于present操作
        tutorialsDataSource.presentingVC = self
        tutorialsTable.delegate = tutorialsDataSource
        tutorialsTable.dataSource = tutorialsDataSource
    }
}

// 继承NSObject而非UIViewController,仅实现数据源/代理协议
class TutorialsTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    // 用弱引用持有主控制器,避免循环引用
    weak var presentingVC: UIViewController?

    struct Tutorials {
        let name: String
        let description: String
    }
    let data: [Tutorials] = [
        Tutorials(name: "ParkProfessorHelp", description: "Park Professor Features"),
        Tutorials(name: "LightningLane", description: "Booking and Modifying Lightning Lane Reservations"),
        Tutorials(name: "MobileOrder", description: "Guide to Mobile Ordering Food"),
        Tutorials(name: "Parking", description: "Parking Tips")
    ]

    // MARK: - UITableView DataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let tutorials = data[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "tutorialstableCell", for: indexPath) as! tutorialstableCell
        cell.tutorialsImage.image = UIImage(named: tutorials.name + ".png")
        cell.tutorialsLabel.text = tutorials.description
        return cell
    }

    // MARK: - UITableView Delegate
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 先校验主控制器是否存在
        guard let presentingVC = presentingVC else {
            print("未设置负责present的控制器")
            return
        }
        let tutorials = data[indexPath.row]
        let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        guard let popupVC = mainStoryboard.instantiateViewController(withIdentifier: tutorials.name) else {
            print("未找到标识符为\(tutorials.name)的ViewController")
            return
        }
        popupVC.modalPresentationStyle = .popover
        // 用主控制器执行present操作
        presentingVC.present(popupVC, animated: true, completion: nil)
    }
}

class tutorialstableCell: UITableViewCell {
    @IBOutlet weak var tutorialsImage: UIImageView!
    @IBOutlet weak var tutorialsLabel: UILabel!
}

内容的提问来源于stack exchange,提问作者Wasko Ltd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:19:57