重新加载 UICollectionView 的部分并保留滚动位置

Has*_*h88 4 objective-c ios uicollectionview

我有一个水平UICollectionView的几个部分,每个部分包含几个单元格,如下所示:

第 0 部分:取消选择的一个单元格。
第 1 部分:最近选择的项目。
第 2 部分及以后:每个部分都有几个可以选择的项目。

When a cell from section 2 or later is selected, a copy of that item is inserted to the start section 1 in the data source, and I want to reload section 1 to reflect the updated recent items. 但是,我想保留滚动位置。我试过了:

[collectionView reloadSections:setWithIndex1]
[collectionView reloadItemsAtIndexPaths:arrayWithAllSection1IndexPaths]
我已经尝试使用[collectionView performBatchUpdates:],但是我尝试过的所有操作都使滚动偏移量重置为集合视图的开头。我已经尝试通过使用基本集合视图启动一个新应用程序并使用 重新加载一个部分来进行完整性检查reloadSections,并且它具有重置滚动偏移的期望行为。但是在我现有的代码库中做同样的事情确实会重置偏移量。

我已经倾注了我的collectionView相关代码来寻找reloadData's、setContentOffsets's 和类似的东西,但对于我的生活,我找不到导致它的原因。有什么我遗漏的东西可以在更新后重置滚动位置吗?

bev*_*voy 5

如果您可以在没有任何动画的情况下执行此操作,我将执行以下操作:

  • 从禁用动画开始
UIView.setAnimationsEnabled(false)
Run Code Online (Sandbox Code Playgroud)
  • 在插入新项目(单元格)之前,存储所选单元格的可见矩形的值
let selectedCell = collectionView.cellForItem(at: indexPath)
let visibleRect = collectionView.convert(collectionView.bounds, to: selectedCell)
Run Code Online (Sandbox Code Playgroud)
  • 执行插入,完成后,检查所选单元格的新可见矩形是什么,将其与旧值进行比较并将它们的差异添加到 collectionView 的 contentOffset。
selectedObjects.insert(allObjects[indexPath.item], at: 0)
collectionView.performBatchUpdates({

   // INSERTING NEW ITEM
   let indexPathForNewItem = IndexPath(item: 0, section: 1)
   collectionView.insertItems(at: [indexPathForNewItem])
}) { (finished) in

   // GETTING NEW VISIBLE RECT FOR SELECTED CELL
   let updatedVisibleRect = collectionView.convert(collectionView.bounds, to: selectedCell)

   // UPDATING COLLECTION VIEW CONTENT OFFSET
   var contentOffset = collectionView.contentOffset
   contentOffset.x = contentOffset.x + (visibleRect.origin.x - updatedVisibleRect.origin.x)
   collectionView.contentOffset = contentOffset
}
Run Code Online (Sandbox Code Playgroud)
  • 通过启用动画来完成
UIView.setAnimationsEnabled(true)
Run Code Online (Sandbox Code Playgroud)

我在根据您描述的行为调整的简单集合视图上进行了尝试。这是整个实现(collecionView 在故事板中,所以如果你想测试我的解决方案,不要忘记连接插座。)

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    let reuseIdentifier = "cell.reuseIdentifier"

    var allObjects: [UIColor] = [.red, .yellow, .orange, .purple, .blue]
    var selectedObjects: [UIColor] = []


    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: self.reuseIdentifier)
    }
}

extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 3
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        switch section {
        case 0: return 1
        case 1: return selectedObjects.count
        case 2: return allObjects.count
        default: return 0
        }
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier, for: indexPath)

        switch indexPath.section {
        case 0: cell.contentView.backgroundColor = .black
        case 1: cell.contentView.backgroundColor = selectedObjects[indexPath.item]
        case 2: cell.contentView.backgroundColor = allObjects[indexPath.item]
        default: break
        }

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150, height: collectionView.frame.size.height)
    }
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: false)

        switch indexPath.section {
        case 0:
            self.selectedObjects.removeAll()
            collectionView.reloadData()
        case 2:
            if selectedObjects.contains(allObjects[indexPath.item]) {
                break
            } else {
                // SOLUTION //
                UIView.setAnimationsEnabled(false)
                let selectedCell = collectionView.cellForItem(at: indexPath)
                let visibleRect = collectionView.convert(collectionView.bounds, to: selectedCell)

                selectedObjects.insert(allObjects[indexPath.item], at: 0)
                collectionView.performBatchUpdates({
                    let indexPathForNewItem = IndexPath(item: 0, section: 1)
                    collectionView.insertItems(at: [indexPathForNewItem])
                }) { (finished) in
                    let updatedVisibleRect = collectionView.convert(collectionView.bounds, to: selectedCell)

                    var contentOffset = collectionView.contentOffset
                    contentOffset.x = contentOffset.x + (visibleRect.origin.x - updatedVisibleRect.origin.x)
                    collectionView.contentOffset = contentOffset
                }
                UIView.setAnimationsEnabled(true)
                // END OF SOLUTION //
            }

        default:
            break
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

我也试过更换

let indexPathForNewItem = IndexPath(item: 0, section: 1)
collectionView.insertItems(at: [indexPathForNewItem])
Run Code Online (Sandbox Code Playgroud)

collectionView.reloadSections(IndexSet(integer: 1))
Run Code Online (Sandbox Code Playgroud)

它也工作得很好,没有任何闪烁,所以这取决于你,哪个对你更方便。