减少单元格数会导致NSInternalInconsistencyException异常

时间:2017-07-23 13:53:06

标签: swift swift3 uicollectionview

我已经在UICollectionView的自定义布局上工作了一段时间。到目前为止,我已经设法在左侧放置一个标题,使其变得粘滞以避免滚动,并且我还制作了一个放大输出算法。

在我的自定义UICollectionView中,我UIPinchGestureRecognizer告诉我们是否放大/缩小。

        let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(zoom(_:)))
        pinchRecognizer.delegate = self
        isUserInteractionEnabled = true
        collectionView.addGestureRecognizer(pinchRecognizer)

func zoom(_ gesture: UIPinchGestureRecognizer) {
        let scale = gesture.scale


        if gesture.state == .ended {
            if scale > 1 {
                self.lastZoomScale += self.zoomScale
            } else if scale < 1 {
                self.lastZoomScale -= self.zoomScale
            }
        }

        collectionView.performBatchUpdates({
            if scale > 1 {
                self.flowLayout.isZoomed = true
                self.zoomScale = scale * 4
                // If last zoom is equal 0 then user does not zoom it yet.
                if self.lastZoomScale == 0 {
                    // Assign current zoom.
                    self.flowLayout.previousCellCount = Int(self.zoomScale)
                    self.flowLayout.numberOfCells = Int(self.zoomScale)
                } else {
                    // User did scroll before and max of zooming scale might be 24 becouse of 24 hours.
                    if self.lastZoomScale > 24 {
                        self.lastZoomScale = 24
                    }
                    self.flowLayout.previousCellCount = Int(self.lastZoomScale)
                    self.flowLayout.numberOfCells = Int(self.lastZoomScale)
                }
            } else if scale < 1 {
                self.zoomScale = scale * 4
                // User did not zoom in then lets mark it as a default number of cells.
                if self.lastZoomScale == 0 {
                    self.flowLayout.numberOfCells = 4
                } else {
                    let scrollingDifference = self.lastZoomScale - self.zoomScale
                    if scrollingDifference > 4 {
                        self.flowLayout.previousCellCount = Int(self.lastZoomScale)
                        self.flowLayout.isZoomed = false
                        self.flowLayout.numberOfCells = Int(scrollingDifference)
                    } else {
                        self.flowLayout.numberOfCells = 4
                    }
                }
            }
        }) { _ in

            // TODO: Change size of cells while zoomed in/out.

//            self.collectionView.performBatchUpdates({
//                self.flowLayout.zoomMultiplier = 2
//            })
        }
    }

从第一个陈述中我们可以看到,当状态结束时,增加lastZoomScale以增加规模,并在我们缩小回原始规模时减少。

稍后UICollectionView以最少的4个可见单元格为例,最大值为24个。从评论中我们可以看到发生了什么。在完成声明中也有TODO,但我们对此并不感兴趣。

自定义流程布局的代码时间如下:

import UIKit

class MyCollectionViewFlowLayout: UICollectionViewFlowLayout {

    private var cellAttributes = [UICollectionViewLayoutAttributes]()
    private var headerAttributes = [UICollectionViewLayoutAttributes]()

    private var newIndexPaths = [IndexPath]()
    private var removedIndexPaths = [IndexPath]()

    private let numberOfSections = 4
    private let headerWidth: CGFloat = 75
    private let verticalDividerWidth: CGFloat = 1
    private let horizontalDividerHeight: CGFloat = 1

    fileprivate var contentSize: CGSize?

    static let numberOfVisibleCells: Int = 4

    var previousCellCount: Int = 0 {
        willSet(newValue) {
            self.previousCellCount = newValue
        }
    }

    var numberOfCells: Int = 4 {
        willSet(newValue) {
            self.numberOfCells = newValue
        } didSet {
            invalidateLayout()
        }
    }

    var zoomMultiplier: CGFloat = 1 {
        willSet(newValue) {
            self.zoomMultiplier = newValue
        } didSet {
            invalidateLayout()
        }
    }

    var isZoomed: Bool = false {
        willSet(newValue) {
            self.isZoomed = newValue
        }
    }

    override init() {
        super.init()
    }

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

    override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
        removedIndexPaths.removeAll()
        super.prepare(forCollectionViewUpdates: updateItems)
        var howManyToDelete = previousCellCount - numberOfCells
        while howManyToDelete > 1 {
            removedIndexPaths.append(cellAttributes.last!.indexPath)
            howManyToDelete -= 1
        }
    }

    override func prepare() {
        scrollDirection = .horizontal
        minimumLineSpacing = 1
        minimumInteritemSpacing = 1
        sectionHeadersPinToVisibleBounds = true
        if collectionView == nil { return }

        cellAttributes.removeAll()
        headerAttributes.removeAll()

        var cellX: CGFloat = 0
        let xOffset = collectionView!.contentOffset.x

        // Calculate the height of a row.
        let availableHeight = collectionView!.bounds.height - collectionView!.contentInset.top - collectionView!.contentInset.bottom - CGFloat(numberOfSections - 1) * horizontalDividerHeight

        let rowHeight = availableHeight / CGFloat(numberOfSections)

        // Calculate the width available for time entry cells.
        let itemsWidth: CGFloat = collectionView!.bounds.width - collectionView!.contentInset.left - collectionView!.contentInset.right - headerWidth

        // For each section.
        for i in 0..<numberOfSections {
            // Y coordinate of the row.
            let rowY = CGFloat(i) * (rowHeight + horizontalDividerHeight)

            // Generate and store layout attributes header cell.
            let headerIndexPath = IndexPath(item: 0, section: i)

            let headerCellAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: headerIndexPath)

            headerAttributes.append(headerCellAttributes)

            headerCellAttributes.frame = CGRect(x: 0, y: rowY, width: headerWidth, height: rowHeight)
            // Sticky header while scrolling.
            headerCellAttributes.zIndex = 1
            headerCellAttributes.frame.origin.x = xOffset
            // TODO: Count of the cells for each section.
            // Guesing it is going to be 24 = 24hs per day.

            // Set the initial X position for cell.
            cellX = headerWidth

            // For each cell set a width.
            for j in 0..<numberOfCells {

                //Get the width of the cell.
                var cellWidth = CGFloat(Double(itemsWidth) / Double(numberOfCells)) * zoomMultiplier
                cellWidth -= verticalDividerWidth
                cellX += verticalDividerWidth

                // Generate and store layout attributes for the cell
                let cellIndexPath = IndexPath(item: j, section: i)
                let entryCellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)

                cellAttributes.append(entryCellAttributes)
                entryCellAttributes.frame = CGRect(x: cellX, y: rowY, width: cellWidth, height: rowHeight)
                cellX += cellWidth
            }
        }
        contentSize = CGSize(width: cellX, height: collectionView!.bounds.height)
    }

    override var collectionViewContentSize: CGSize {
        get {
            if contentSize != nil {
                return contentSize!
            }
            return .zero
        }
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        // FIXME: Zoom-out crash.
        return cellAttributes.first(where: { attributes -> Bool in
            return attributes.indexPath == indexPath
        })
    }

    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return headerAttributes.first(where: { attributes -> Bool in
            return attributes.indexPath == indexPath
        })
    }

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

        for attribute in headerAttributes {
            if attribute.frame.intersects(rect) {
                attributes.append(attribute)
            }
        }

        for attribute in cellAttributes {
            if attribute.frame.intersects(rect) {
                attributes.append(attribute)
            }
        }

        print("Inside layout attribs \(cellAttributes.count)")

        return attributes
    }

    override func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -> [IndexPath] {
        return removedIndexPaths
    }

    override func finalizeCollectionViewUpdates() {
        removedIndexPaths.removeAll()
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

正如您所见,我已设法将评论粘贴到代码中。

我的问题是,无论何时我增加numberOfCells,它都会像魅力一样,但每当我要减少它们的数量时,它会崩溃,但有例外:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForItemAtIndexPath: <NSIndexPath: 0xc000000001600016> {length = 2, path = 0 - 11}'

我已经删除了这些索引(prepare(forCollectionViewUpdates...),稍后会在indexPathsToDeleteForSupplementaryView内),并且不知道那里发生了什么。

任何人都知道如何解决这个问题?

谢谢!

0 个答案:

没有答案