如何重写scrollViewDidEndDecelerating并获取当前可见Cell?
我有一个包含UICollectionView的视图控制器,代码如下:
class TagViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { @IBOutlet weak var collectionView: UICollectionView! func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return posts.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let post = posts[indexPath.row] let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SNPostViewCell cell.isVideo = post.isVideo cell.mediaURL = URL(string: post.mediaURL) cell.backgroundColor = UIColor.black return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let post = posts[indexPath.row] if post.isVideo == true { self.performSegue(withIdentifier: "playVideo", sender: nil) } else { print("is image. might go full screen one day here") } let bin = self.bins[post.timeStamp] print(bin) } override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { //Error: method cannot override from its superclass <#code#> } }
虽然我能重写那些UICollectionView相关方法,但重写scrollViewDidEndDecelerating时提示无法重写父类方法。
于是我创建了一个继承自UICollectionViewController的自定义集合视图控制器,这里能正确重写scrollViewDidEndDecelerating方法,但遇到了新问题:我需要获取当前Cell的索引,而这个索引对应的数据源(比如posts数组)只在原来的TagViewController里定义,我不知道该把这个重写方法放在哪里,以及如何成功获取对应的数据索引。
自定义集合视图控制器的代码片段:
class PagingCollectionViewController: UICollectionViewController { ... override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { if (self.collectionView.contentOffset.y < 0) { self.collectionView.contentOffset = CGPointMake(self.collectionView.contentOffset.x, 0.0); } for cell in yourCollectionViewname.visibleCells() as [UICollectionViewCell] { let indexPath = yourCollectionViewname.indexPathForCell(cell as UICollectionViewCell) } } ... }
其实你完全不用切换到UICollectionViewController就能解决问题!先给你理清第一个报错的原因:scrollViewDidEndDecelerating不是UIViewController的父类方法,它是UIScrollViewDelegate协议里的方法——而UICollectionViewDelegate已经继承了UIScrollViewDelegate,所以你只需要去掉override关键字,直接实现这个方法就行!
方案一:直接在原ViewController中处理滚动事件
这是最省心的方案,不用重构代码,还能直接访问当前视图控制器里的数据源:
class TagViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { @IBOutlet weak var collectionView: UICollectionView! // 其他已有的方法保持不变... // 去掉override,直接实现UIScrollViewDelegate的方法 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { // 先处理偏移量的修正逻辑 if scrollView.contentOffset.y < 0 { scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0) } // 获取当前可见的cell及其索引 for cell in collectionView.visibleCells { if let indexPath = collectionView.indexPath(for: cell) { // 直接用你的posts数组获取对应数据 let post = posts[indexPath.row] print("当前可见的帖子: \(post)") // 也能直接访问bins字典 if let bin = bins[post.timeStamp] { print("对应的bin数据: \(bin)") } } } } }
方案二:如果坚持使用UICollectionViewController
如果你还是想基于UICollectionViewController来实现,那只需要给它添加数据源属性,把原视图控制器里的数据传递过去即可:
首先修改自定义集合视图控制器:
// 替换PostType和BinType为你实际的类型 class PagingCollectionViewController: UICollectionViewController { // 添加数据源属性 var posts: [PostType] = [] var bins: [String: BinType] = [:] override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { if scrollView.contentOffset.y < 0 { scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0) } for cell in collectionView.visibleCells { if let indexPath = collectionView.indexPath(for: cell) { let post = posts[indexPath.row] print("当前帖子: \(post)") if let bin = bins[post.timeStamp] { print("对应的bin数据: \(bin)") } } } } // 别忘了实现UICollectionView的数据源方法,逻辑和原TagViewController一致 override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return posts.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let post = posts[indexPath.row] let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "reuseIdentifier", for: indexPath) as! SNPostViewCell cell.isVideo = post.isVideo cell.mediaURL = URL(string: post.mediaURL) cell.backgroundColor = .black return cell } }
然后在TagViewController里跳转或初始化这个控制器时,传递数据:
// 比如在segue跳转时传递 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let pagingVC = segue.destination as? PagingCollectionViewController { pagingVC.posts = self.posts pagingVC.bins = self.bins } } // 如果是代码创建控制器 let pagingVC = PagingCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout()) pagingVC.posts = self.posts pagingVC.bins = self.bins navigationController?.pushViewController(pagingVC, animated: true)
总结
优先选方案一,因为它不需要重构现有代码,还能直接访问原视图控制器的数据源,避免了数据传递的麻烦。核心就是记住:scrollViewDidEndDecelerating是协议方法,不是父类的重写方法,所以不用加override关键字。
内容的提问来源于stack exchange,提问作者BigBoy1337

