UICollectionView(自动大小).reloadData()将contentOffset重置为0(顶部)

时间:2017-10-19 10:01:46

标签: ios swift uicollectionview autolayout flowlayout

我有UICollectionView使用标准UICollectionViewFlowLayoutestimatedItemSize设置为UICollectionViewFlowLayoutAutomaticSize

我致电collectionView.reloadData()后,视图的滚动位置会重置为CGPoint(x: 0, y: 0)。我知道.reloadData()应该只作为最后的手段使用,但在我的场景中这是必要的。到目前为止我发现它可能是因为没有通过collectionView(_:layout:sizeForItemAt:)返回项目大小。 (任何与UITableView tableView:estimatedHeightForRowAtIndexPath: in this StackOverflow answer类似的解决方案都会受到高度赞赏。

5 个答案:

答案 0 :(得分:2)

我遇到了同样的问题,并通过对集合视图进行子类化,覆盖reloadData来临时存储当前contentOffset,然后侦听对contentOffset的更改并在我的临时偏移变量中存储了任何内容的情况下将其更改为

class MyCollectionView : UICollectionView {
    deinit {
        removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset))
    }

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        self.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.old, .new], context: nil)
    }

    private var temporaryOffsetOverride : CGPoint?
    override func reloadData() {
        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout, flowLayout.estimatedItemSize == UICollectionViewFlowLayout.automaticSize {
            temporaryOffsetOverride = contentOffset
        }
        super.reloadData()
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == #keyPath(UIScrollView.contentOffset) {
            if let offset = temporaryOffsetOverride {
                temporaryOffsetOverride = nil
                self.setContentOffset(offset, animated: false)
            }
        }
    }
}

答案 1 :(得分:1)

在我的情况下,我将其固定为: 在这里,我检测我的细胞是否完全可见,如果不是,则移至该位置。 (没有“跳转”)

 let cellRect = collectionView.convert(cell.frame, to: collectionView.superview)
      if !collectionView.frame.contains(cellRect) {
        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
        return collectionView.reloadData()
      }

在这里,我保持我的contentOffset:

   let currentContentOffSet = collectionView.contentOffset
         if #available(iOS 12.0, *) {
            UIView.animate(withDuration: 0) {
               collectionView.reloadData()
               collectionView.performBatchUpdates(nil, completion: { _ in
                  collectionView.contentOffset = currentContentOffSet
               })
            }
         } else {
            UIView.animate(withDuration: 0) {
               collectionView.reloadItems(at: [self.lastSelectedIndexPath, indexPath])
               collectionView.contentOffset = currentContentOffSet
            }
         }

如果单元最近(或已经可见)可见并且我点击该单元,则有助于移动到单元。也没有“跳转”(无需重置contentOffset)

您还可以在collectionViewController中尝试以下操作:

  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
      guard let cell = collectionView.cellForItem(at: indexPath) else { return }
      if lastSelectedIndexPath != indexPath {
         let cellRect = collectionView.convert(cell.frame, to: collectionView.superview)
         if !collectionView.frame.contains(cellRect) {
            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
         } else {
            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .init())
         }
         collectionView.deselectItem(at: lastSelectedIndexPath, animated: false)
         lastSelectedIndexPath = indexPath
      }
   }

,这是collectionViewCellModel中的:

  override var isSelected: Bool {
      didSet {
         switch self.isSelected {
         case false:

         case true:

         }
      }
   }

答案 2 :(得分:0)

reloadData重新加载UICollectionView中的所有单元格,因此,如果您对单元格有任何引用,它将指向不存在的对象,如果它是强引用,则可能会导致保留周期。所以:

你不应该这样做!

但是...

很难想象需要reloadData的场景,并且无法通过插入和删除对象来替换它。

所以你可能想要使用的是重新加载单行或插入它在数据模型中更改的项目。您没有在详细信息中发布您的问题,但您可以执行前面提到的任何操作,例如:

        collectionView.performBatchUpdates({
            collectionView.insertItems(at: <#T##[IndexPath]#>)
            collectionView.deleteItems(at: <#T##[IndexPath]#>)
            collectionView.reloadItems(at: <#T##[IndexPath]#>)
        }, completion: { (<#Bool#>) in
            <#code#>
        })

但请确保您先编辑数据模型。所以:

var dataModel: [String] = ["0","1","2"]   //Data model for Collection View
collectionView.reloadData() // In that point you will have 3 cells visible on the sceen
.
.
.
dataModel.removeFirst() // now your data model is ["1","2"]
collectionView.performBatchUpdates({
    collectionView.deleteItems(at: [IndexPath(row: 0, section:0)])
}, completion: { success in 
    //some check consistency code
})

由此,您的收集视图将保持不变,除第一行外不会进行任何其他更改。

答案 3 :(得分:0)

重新加载 UICollectionView 的部分对我有用。

UIView.performWithoutAnimation {self.collStages.reloadSections([0])}
collStages.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)

答案 4 :(得分:-1)

不确定它是否可行,但您可以尝试一下:

    let currentOffset = self.collectionView.contentOffset
    self.collectionView.reloadData()
    self.collectionView.setContentOffset(currentOffset, animated: false)

知道吗?谢谢:))