ios – 滚动双向滚动UICollectionView并具有大量单元格(250,000或更多)时可见延迟

我是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
}
}

相关文章

UITabBarController 是 iOS 中用于管理和显示选项卡界面的一...
UITableView的重用机制避免了频繁创建和销毁单元格的开销,使...
Objective-C中,类的实例变量(instance variables)和属性(...
从内存管理的角度来看,block可以作为方法的传入参数是因为b...
WKWebView 是 iOS 开发中用于显示网页内容的组件,它是在 iO...
OC中常用的多线程编程技术: 1. NSThread NSThread是Objecti...