多层导航层级下SongsTableViewController无法正常显示UISearchController
解决导航栈多层视图中UISearchController激活失败的问题
这个问题我之前也碰到过,本质是导航栈中视图控制器的搜索控制器状态冲突,加上系统对搜索控制器展示上下文的判断逻辑导致的。咱们一步步拆解原因和解决方案:
核心问题分析
你遇到的报错"试图在视图不在窗口层级的AlbumsCollectionViewController上展示UISearchController",根源在于:
- 导航栈中上层的AlbumsCollectionViewController的搜索控制器可能还残留激活状态或关联关系,导致系统误将SongsTableViewController的搜索操作关联到它;
- 默认的
UISearchController属性obscuresBackgroundDuringPresentation是true,激活时会尝试隐藏导航栈中所有下层视图,这会让系统混淆当前应该展示搜索控制器的上下文; - 直接压入SongsVC时没问题,是因为此时导航栈中没有其他带搜索控制器的VC,系统能正确识别上下文。
分步解决方案
1. 给SongsTableViewController配置独立且正确的搜索控制器
确保每个VC的搜索控制器是独立实例,并且设置关键属性:
import UIKit class SongsTableViewController: UITableViewController, UISearchResultsUpdating, UISearchControllerDelegate { // 独立初始化搜索控制器 private lazy var searchController: UISearchController = { let searchVC = UISearchController(searchResultsController: nil) searchVC.searchResultsUpdater = self // 必须设为false!避免干扰导航栈中其他VC的视图层级 searchVC.obscuresBackgroundDuringPresentation = false searchVC.searchBar.placeholder = "搜索歌曲" // 设置代理以便处理取消事件 searchVC.delegate = self return searchVC }() override func viewDidLoad() { super.viewDidLoad() // 初始设置普通标题 title = "歌曲列表" // 添加搜索按钮到导航栏右侧 navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .search, target: self, action: #selector(showSearchBar) ) } // 点击按钮展示并激活搜索栏 @objc private func showSearchBar() { // 先确保当前VC是导航栈的顶部,避免系统找错上下文 guard navigationController?.topViewController === self else { return } // 将titleView替换为搜索栏 navigationItem.titleView = searchController.searchBar // 激活搜索控制器 searchController.isActive = true // 让搜索栏自动获取焦点 searchController.searchBar.becomeFirstResponder() } // 实现搜索结果更新代理 func updateSearchResults(for searchController: UISearchController) { // 这里写你的搜索逻辑,比如过滤歌曲列表 guard let searchText = searchController.searchBar.text else { return } // ... } // 处理搜索控制器取消事件,恢复原标题 func didDismissSearchController(_ searchController: UISearchController) { navigationItem.titleView = nil title = "歌曲列表" } }
2. 清理AlbumsCollectionViewController的搜索控制器残留状态
在AlbumsVC被push到SongsVC之前,确保它的搜索控制器已经取消激活并恢复视图状态,避免干扰后续页面:
class AlbumsCollectionViewController: UICollectionViewController { // ... 你的其他代码 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 离开页面时如果搜索控制器还激活,就取消并恢复视图 if searchController.isActive { searchController.isActive = false navigationItem.titleView = nil title = "专辑列表" } } }
关键注意点
- 永远不要在多个VC之间共享同一个
UISearchController实例,每个VC都要单独初始化; obscuresBackgroundDuringPresentation = false是多层导航栈中使用搜索控制器的核心配置,一定要设置;- 激活搜索控制器前,先校验当前VC是导航栈的顶部,确保系统使用正确的上下文。
内容的提问来源于stack exchange,提问作者MrGreen
相关产品推荐
相关产品推荐

