自定义UICollectionViewLayout为不存在的索引路径传递layoutattributes

时间:2018-06-05 08:01:00

标签: ios swift uicollectionview

我正在尝试创建一个自定义的UICollectionViewFlowLayout,其中项目可以垂直或水平显示(见下文)

垂直:

| S | S | S |
| 1 | 4 | 7 |
| 2 | 5 | 6 |
| 3 | 6 | 9 |

水平:

| S | 1 | 2 | 3 |
| S | 4 | 5 | 6 |
| S | 7 | 8 | 9 |

但是,我似乎无法使垂直布局正常工作。水平布局工作得很好,但每次我想使用垂直布局时,它都会崩溃并出现以下错误:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0x7ffcf597fa60> {length = 2, path = 1 - 6}'

当我使用相同的数据并强制它使用水平布局时,它工作正常,这让我相信我在做布局属性时出错了。

我的自定义流程布局类:

import UIKit

class CustomCollectionViewLayout: UICollectionViewFlowLayout {

var horizontalInset = 0.0 as CGFloat
var verticalInset = 0.0 as CGFloat

var minimumItemWidth = 0.0 as CGFloat
var maximumItemWidth = 0.0 as CGFloat

var itemHeight = 0.0 as CGFloat
var itemWidth = 0.0 as CGFloat

var initialItemSizeSet: Bool = false

var _layoutAttributes = Dictionary<String, UICollectionViewLayoutAttributes>()
var _contentSize = CGSize.zero

var transposed: Bool

init(transposed: Bool) {
    self.transposed = transposed

    super.init()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func prepare() {
    super.prepare()

    _layoutAttributes = Dictionary<String, UICollectionViewLayoutAttributes>()
    _layoutAttributes.removeAll()

    let path = IndexPath(item: 0, section: 0)
    let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: path)

    let headerHeight = CGFloat(self.itemHeight / 4)
    attributes.frame = CGRect(x: 0, y: 0, width: self.collectionView!.frame.size.width, height: headerHeight)

    let headerKey = layoutKeyForHeaderAtIndexPath(path)
    _layoutAttributes[headerKey] = attributes

    let numberOfSections = (self.collectionView!.numberOfSections)-1

    var yOffset = headerHeight
    var xOffset = self.horizontalInset

    var largestNumberOfItems = 0

    for section in 0...numberOfSections {
        let numberOfItems = self.collectionView!.numberOfItems(inSection: section)

        if(numberOfItems > largestNumberOfItems){
            largestNumberOfItems = numberOfItems
        }

        for item in 0...numberOfItems {
            let indexPath = IndexPath(item: item, section: section)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)

            var itemSize = CGSize.zero

            //Every cell has a fixed size
            if(!initialItemSizeSet){
                self.itemWidth = 75
                self.itemHeight = 75
                initialItemSizeSet = true
            }

            itemSize.width = itemWidth
            itemSize.height = itemHeight

            attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemSize.width, height: itemSize.height).integral
            print("Created frame with xOffset \(xOffset), yOffset \(yOffset), width \(itemSize.width) and height \(itemSize.height)")
            let key = layoutKeyForIndexPath(indexPath)
            print("Attached indexPath key: \(key)")
            _layoutAttributes[key] = attributes

            if transposed {
                yOffset += itemSize.height
                yOffset += self.verticalInset
            }else {
                xOffset += itemSize.width
                xOffset += self.horizontalInset
            }

        }

        //After going through all items in section, check if current section is not the last section
        if !(section == numberOfSections) {
            if transposed {
                //Increase xOffset for correct horizontal placement
                xOffset += self.horizontalInset
                xOffset += self.itemWidth

                //Reset yOffset for correct spacing between cells
                yOffset = self.verticalInset
            }else {
                //Increase yOffset for correct vertical placement
                yOffset += self.verticalInset
                yOffset += self.itemHeight

                //Reset xOffset for correct spacing between cells
                xOffset = self.horizontalInset
            }
            print("Finished adding items in section \(section), xOffset: \(xOffset), yOffset: \(yOffset)")
        }else{
            if transposed {
                yOffset = self.verticalInset
            }else {
                xOffset = self.horizontalInset
            }
        }
    }

    if transposed {
        xOffset += self.itemWidth
    }else {
        yOffset += self.itemHeight
    }

    //Calculate size of content based on largest number of items in the sections
    print("xOffset: \(xOffset), yOffset: \(yOffset)")
    if transposed {
        _contentSize = CGSize(width: xOffset + self.horizontalInset, height: (CGFloat(largestNumberOfItems) * (itemHeight + yOffset)))
    }else {
        _contentSize = CGSize(width: (CGFloat(largestNumberOfItems) * (itemWidth + xOffset)), height: yOffset + self.verticalInset)
    }

    print("content size :\(_contentSize)")
}

func changeItemSize(_ newWidth: CGFloat, newHeight: CGFloat){
    self.itemHeight = newHeight
    self.itemWidth = newWidth
}

func changeInset(_ newHorizontalInset: CGFloat, newVerticalInset: CGFloat){
    self.horizontalInset = newHorizontalInset
    self.verticalInset = newVerticalInset
}

func layoutKeyForIndexPath(_ indexPath : IndexPath) -> String {
    return "\(indexPath.section)_\(indexPath.row)"
}

func layoutKeyForHeaderAtIndexPath(_ indexPath : IndexPath) -> String {
    return "s_\(indexPath.section)_\(indexPath.row)"
}

override var collectionViewContentSize : CGSize {
    return _contentSize
}

override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

    let headerKey = layoutKeyForIndexPath(indexPath)
    return _layoutAttributes[headerKey]
}

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

    let key = layoutKeyForIndexPath(indexPath)
    return _layoutAttributes[key]
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    let predicate = NSPredicate {  [unowned self] (evaluatedObject, bindings) -> Bool in
        let layoutAttribute = self._layoutAttributes[evaluatedObject as! String]
        return rect.intersects(layoutAttribute!.frame)
    }

    let dict = _layoutAttributes as NSDictionary
    let keys = dict.allKeys as NSArray
    let matchingKeys = keys.filtered(using: predicate)

    return dict.objects(forKeys: matchingKeys, notFoundMarker: NSNull()) as? [UICollectionViewLayoutAttributes]
}
}

1 个答案:

答案 0 :(得分:0)

事实证明,崩溃的原因是我忘记了集合视图numberOfItems(inSection)返回的项目数量不是从零开始的。我没想到要看那里,因为水平布局由于某种未知原因处理了这种不一致性。