如何在滚动CollectionView内的TableView时隐藏导航栏
这个问题我之前做项目时也碰到过,核心难点就是嵌套层级里的滚动事件怎么传递到顶层的ViewController来控制导航栏。推荐用代理模式来做解耦,这是iOS开发里处理组件间通信的常规操作,下面一步步来实现:
步骤1:给CollectionViewCell定义代理协议
先创建一个协议,让Cell能把内部TableView的滚动事件传递出去:
protocol NestedTableViewScrollDelegate: AnyObject { func nestedTableViewDidScroll(_ scrollView: UIScrollView) }
步骤2:实现CollectionViewCell,处理TableView滚动
在你的CollectionViewCell类里,把TableView的代理设为自己,然后在滚动回调里通知代理:
class CustomCollectionViewCell: UICollectionViewCell { // 假设你的TableView已经通过IB或者代码初始化完成 @IBOutlet weak var nestedTableView: UITableView! weak var scrollDelegate: NestedTableViewScrollDelegate? override func awakeFromNib() { super.awakeFromNib() nestedTableView.delegate = self // 这里可以补充TableView的数据源配置、样式设置等 } } // 扩展实现UITableViewDelegate(本质是UIScrollViewDelegate) extension CustomCollectionViewCell: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // 把滚动事件传递给代理对象 scrollDelegate?.nestedTableViewDidScroll(scrollView) } }
步骤3:ViewController配置Cell代理并处理导航栏逻辑
在顶层ViewController里,实现CollectionView的数据源方法,同时承接Cell的代理事件,根据滚动方向控制导航栏显示/隐藏:
class MainViewController: UIViewController, UICollectionViewDataSource, NestedTableViewScrollDelegate { @IBOutlet weak var mainCollectionView: UICollectionView! // 记录上一次的滚动偏移量,用来判断滚动方向 private var lastContentOffset: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() mainCollectionView.dataSource = self // 关闭系统自带的导航栏自动隐藏,避免和自定义逻辑冲突 navigationController?.hidesBarsOnSwipe = false } // MARK: - UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 1 // 根据你的实际业务调整数量 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell // 设置代理为当前ViewController cell.scrollDelegate = self return cell } // MARK: - NestedTableViewScrollDelegate func nestedTableViewDidScroll(_ scrollView: UIScrollView) { let currentOffset = scrollView.contentOffset.y // 判断滚动方向:向上滚动则隐藏导航栏,向下则显示 let isScrollingUp = currentOffset > lastContentOffset // 加个阈值,避免轻微滚动就触发导航栏变化 let threshold: CGFloat = 20 if abs(currentOffset - lastContentOffset) > threshold { navigationController?.setNavigationBarHidden(isScrollingUp, animated: true) } // 更新上一次的偏移量 lastContentOffset = currentOffset } }
替代方案:用闭包简化事件传递
如果觉得代理写起来繁琐,也可以用闭包来实现,代码更简洁:
在CustomCollectionViewCell里添加闭包属性:
class CustomCollectionViewCell: UICollectionViewCell { @IBOutlet weak var nestedTableView: UITableView! var scrollHandler: ((UIScrollView) -> Void)? override func awakeFromNib() { super.awakeFromNib() nestedTableView.delegate = self } } extension CustomCollectionViewCell: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { scrollHandler?(scrollView) } }
然后在ViewController的Cell配置方法里设置闭包:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell cell.scrollHandler = { [weak self] scrollView in guard let self = self else { return } let currentOffset = scrollView.contentOffset.y let isScrollingUp = currentOffset > self.lastContentOffset let threshold: CGFloat = 20 if abs(currentOffset - self.lastContentOffset) > threshold { self.navigationController?.setNavigationBarHidden(isScrollingUp, animated: true) } self.lastContentOffset = currentOffset } return cell }
一些注意事项
- 记得给CollectionViewCell和TableView设置正确的重用标识符,避免运行时崩溃。
- 如果TableView有自定义的
contentInset,可以用scrollView.adjustedContentInset来计算实际偏移量,保证判断逻辑准确。 - 闭包方案里一定要用
weak self,避免循环引用导致内存泄漏。 - 如果项目里有多个嵌套的TableView,可以在代理方法里通过
scrollView的实例区分不同的TableView,做针对性处理。
内容的提问来源于stack exchange,提问作者saiteja .saiteja
相关产品推荐
相关产品推荐

