我是UICollectionViewFlowLayout的子类,以便在UICollectionView中进行双向滚动.对于较少数量的行和节数(100-200行和节),滚动工作正常,但是当我在UICollectionView中增加行和节数超过500,即250,000或更多单元格时,滚动时存在明显的滞后.我已经在layoutAttributesForElementsInRect中跟踪了for循环的源代码.我使用Dictionary来保存每个单元格的UICollectionViewLayoutAttributes以避免重新计算它并循环遍历它以从layoutAttributesForElementsInRect返回单元格的属性
import UIKit class LuckGameCollectionViewLayout: UICollectionViewFlowLayout { // Used for calculating each cells CGRect on screen. // CGRect will define the Origin and Size of the cell. let CELL_HEIGHT = 70.0 let CELL_WIDTH = 70.0 // Dictionary to hold the UICollectionViewLayoutAttributes for // each cell. The layout attribtues will define the cell's size // and position (x,y,and z index). I have found this process // to be one of the heavier parts of the layout. I recommend // holding onto this data after it has been calculated in either // a dictionary or data store of some kind for a smooth performance. var cellAttrsDictionary = Dictionary<NSIndexPath,UICollectionViewLayoutAttributes>() // Defines the size of the area the user can move around in // within the collection view. var contentSize = CGSize.zero override func collectionViewContentSize() -> CGSize { return self.contentSize } override func prepareLayout() { // Cycle through each section of the data source. if collectionView?.numberOfSections() > 0 { for section in 0...collectionView!.numberOfSections()-1 { // Cycle through each item in the section. if collectionView?.numberOfItemsInSection(section) > 0 { for item in 0...collectionView!.numberOfItemsInSection(section)-1 { // Build the UICollectionVieLayoutAttributes for the cell. let cellIndex = NSIndexPath(forItem: item,inSection: section) let xPos = Double(item) * CELL_WIDTH let yPos = Double(section) * CELL_HEIGHT let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: cellIndex) cellAttributes.frame = CGRect(x: xPos,y: yPos,width: CELL_WIDTH,height: CELL_HEIGHT) // Save the attributes. cellAttrsDictionary[cellIndex] = cellAttributes } } } } // Update content size. let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT self.contentSize = CGSize(width: contentWidth,height: contentHeight) } override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { // Create an array to hold all elements found in our current view. var attributesInRect = [UICollectionViewLayoutAttributes]() // Check each element to see if it should be returned. for (_,cellAttributes) in cellAttrsDictionary { if CGRectIntersectsRect(rect,cellAttributes.frame) { attributesInRect.append(cellAttributes) } } // Return list of elements. return attributesInRect } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { return cellAttrsDictionary[indexPath]! } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return false } }
编辑:
以下是我在layoutAttributesForElementsInRect方法中提出的更改.
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { // Create an array to hold all elements found in our current view. var attributesInRect = [UICollectionViewLayoutAttributes]() let xOffSet = self.collectionView?.contentOffset.x let yOffSet = self.collectionView?.contentOffset.y let totalColumnCount = self.collectionView?.numberOfSections() let totalRowCount = self.collectionView?.numberOfItemsInSection(0) let startRow = Int(Double(xOffSet!)/CELL_WIDTH) - 10 //include 10 rows towards left let endRow = Int(Double(xOffSet!)/CELL_WIDTH + Double(Utils.getScreenWidth())/CELL_WIDTH) + 10 //include 10 rows towards right let startCol = Int(Double(yOffSet!)/CELL_HEIGHT) - 10 //include 10 rows towards top let endCol = Int(Double(yOffSet!)/CELL_HEIGHT + Double(Utils.getScreenHeight())/CELL_HEIGHT) + 10 //include 10 rows towards bottom for(var i = startRow ; i <= endRow; i = i + 1){ for (var j = startCol ; j <= endCol; j = j + 1){ if (i < 0 || i > (totalRowCount! - 1) || j < 0 || j > (totalColumnCount! - 1)){ continue } let indexPath: NSIndexPath = NSIndexPath(forRow: i,inSection: j) attributesInRect.append(cellAttrsDictionary[indexPath]!) } } // Return list of elements. return attributesInRect }
我已经计算了collectionView的偏移量,并用它来计算屏幕上可见的单元格(使用每个单元格的高度/宽度).我不得不在每一侧添加额外的单元格,以便当用户滚动时没有丢失的单元格.我测试了这个并且性能很好.
解决方法
通过利用已知单元格大小的layoutAttributesForElementsInRect(rect:CGRect),您不需要缓存属性,只需在collectionView请求它们时为给定的rect计算它们.您仍然需要检查0的边界情况和最大的部分/行计数,以避免计算不需要的或无效的属性,但可以在循环周围的where子句中轻松完成.这是一个工作示例,我已经测试了1000个部分x 1000行,它工作得很好,没有滞后于设备:
编辑:我添加了更大的Result,以便在滚动到达之前可以预先计算属性.从您的编辑看起来,您仍然在缓存我认为性能不需要的属性.此外,它会带来更大的内存占用,滚动更多.还有一个原因是你不想从回调中使用提供的CGRect而不是从contentOffset手动计算一个?
class LuckGameCollectionViewLayout: UICollectionViewFlowLayout { let CELL_HEIGHT = 50.0 let CELL_WIDTH = 50.0 override func collectionViewContentSize() -> CGSize { let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT return CGSize(width: contentWidth,height: contentHeight) } override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let biggerRect = rect.insetBy(dx: -2048,dy: -2048) let startIndexY = Int(Double(biggerRect.origin.y) / CELL_HEIGHT) let startIndexX = Int(Double(biggerRect.origin.x) / CELL_WIDTH) let numberOfVisibleCellsInRectY = Int(Double(biggerRect.height) / CELL_HEIGHT) + startIndexY let numberOfVisibleCellsInRectX = Int(Double(biggerRect.width) / CELL_WIDTH) + startIndexX var attributes: [UICollectionViewLayoutAttributes] = [] for section in startIndexY..<numberOfVisibleCellsInRectY where section >= 0 && section < self.collectionView!.numberOfSections() { for item in startIndexX..<numberOfVisibleCellsInRectX where item >= 0 && item < self.collectionView!.numberOfItemsInSection(section) { let cellIndex = NSIndexPath(forItem: item,inSection: section) if let attrs = self.layoutAttributesForItemAtIndexPath(cellIndex) { attributes.append(attrs) } } } return attributes } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { let xPos = Double(indexPath.row) * CELL_WIDTH let yPos = Double(indexPath.section) * CELL_HEIGHT let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) cellAttributes.frame = CGRect(x: xPos,height: CELL_HEIGHT) return cellAttributes } }