UICollectionView不呈现所有行

时间:2017-12-06 16:52:33

标签: ios swift uicollectionview rendering uicollectionviewcell

我正在尝试创建一个简单的应用程序,其中可以为UICollectionView输入多个列和多个行。然后,集合视图计算适合它的可能方块的大小并绘制它们。 我想允许最多32个宽度和64个高度。滚动被禁用,因为整个网格应该立即显示。

例如,4x8看起来像这样

4x8 Grid

和8x4将如下所示

8x4 Grid

因此可以看出工作正常。问题伴随着更多的列和/或行。最多30x8一切都很好但从31开始只绘制了8行中的6个。

31x8 Grid

所以我不明白为什么。以下是我用来计算所有内容的代码:

部分数和行数:

func numberOfSections(in collectionView: UICollectionView) -> Int
{
    let num = Int(heightInput.text!)
    if(num != nil)
    {
        if(num! > 64)
        {
            return 64
        }
        return num!
    }
    return 8
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
    let num = Int(widthInput.text!)
    if(num != nil)
    {
        if(num! > 32)
        {
            return 32
        }
        return num!
    }
    return 4
}

indexPath项目的单元格

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
    let size = calculateCellSize()
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
    var origin = cell.frame.origin
    origin.x = 1+CGFloat(indexPath.row) + size.width*CGFloat(indexPath.row)
    origin.y = 1+CGFloat(indexPath.section) + size.height*CGFloat(indexPath.section)
    cell.frame = CGRect(origin: origin, size: size)
    NSLog("Cell X:%@, Cell Y:%@",origin.x.description,origin.y.description)
    return cell
}

计算尺寸方法

func calculateCellSize() -> CGSize
{
    //First check if we have valid values
    let col = Int(widthInput.text!)
    let row = Int(heightInput.text!)
    if(col == nil || row == nil)
    {
        return CGSize(width: 48.0, height: 48.0)
    }
    //If there are more or equal amount of columns than rows
    let columns = CGFloat(col!)
    let rows = CGFloat(row!)
    if(columns >= rows)
    {
        //Take the grid width
        let gridWidth = drawCollection.bounds.size.width
        //Calculate the width of the "pixels" that fit the width of the grid
        var pixelWidth = gridWidth/columns
        //Remember to substract the inset from the width
        let drawLayout = drawCollection.collectionViewLayout as? UICollectionViewFlowLayout
        pixelWidth -= (drawLayout?.sectionInset.left)! + 1/columns
        return CGSize(width: pixelWidth, height: pixelWidth)
    }
    else
    {
        //Rows are more than columns
        //Take the grid height as reference here
        let gridHeight = drawCollection.bounds.size.height
        //Calculate the height of the "pixels" that fit the height of the grid
        var pixelHeight = gridHeight/rows
        //Remember to substract the inset from the height
        let drawLayout = drawCollection.collectionViewLayout as? UICollectionViewFlowLayout
        pixelHeight -= (drawLayout?.sectionInset.top)! + 1/rows
        return CGSize(width: pixelHeight, height: pixelHeight)
    }
    return CGSize(width: 48.0, height: 48.0)
}

出于调试原因,我将一个计数器放入cellforItemAtIndexPath方法中,实际上我可以看到最后两行没有被调用。计数器结束于185但理论上应该被称为248次,实际上差异将显示为2 * 32 - 1(对于不均匀的31)所以最后丢失的行....

有几件事情让我想到了原因是什么,但似乎没有:

  1. 单元格未在正确的位置绘制(也就是在网格外部) - >至少不正确,因为该方法仅被调用185次。
  2. 计算单元格在网格外部,因此不会尝试由UICollectionView渲染 - >仍然可能,因为我无法证明如何证明。
  3. UICollectionView可以绘制一个(如果可以配置的话)最大元素数量,并且31x8已超过该数量 - >仍有可能找不到任何相关内容。
  4. 总结:

    是否可以显示网格中的所有元素(最大32x64),如果是,那么我的实现有什么问题?

    谢谢大家的时间和答案!

2 个答案:

答案 0 :(得分:1)

你正在做很多计算你不需要做的事情。此外,设置单元格的.frame是一个非常糟糕的主意。集合视图的一个重点是避免必须设置框架。

看看这个:

class GridCollectionViewController: UIViewController, UICollectionViewDataSource {

    @IBOutlet weak var theCV: UICollectionView!

    var numCols = 0
    var numRows = 0

    func updateCV() -> Void {

        // subtract the number of colums (for the 1-pt spacing between cells), and divide by number of columns
        let w = (theCV.frame.size.width - CGFloat((numCols - 1))) / CGFloat(numCols)

        // subtract the number of rows (for the 1-pt spacing between rows), and divide by number of rows
        let h = (theCV.frame.size.height - CGFloat((numRows - 1))) / CGFloat(numRows)

        // get the smaller of the two values
        let wh = min(w, h)

        // set the cell size
        if let layout = theCV.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.itemSize = CGSize(width: wh, height: wh)
        }

        // reload the collection view
        theCV.reloadData()

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // start with a 31x20 grid, just to see it
        numCols = 31
        numRows = 20

        updateCV()
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return numRows
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numCols
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = theCV.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

        return cell
    }

    @IBAction func btnTap(_ sender: Any) {
        // example of changing the number of rows/columns based on user action
        numCols = 32
        numRows = 64
        updateCV()
    }

}

答案 1 :(得分:0)

您需要开发自己的UICollectionViewLayout。通过这种方法,您可以实现人类可以想象的任何结果。

import UIKit

class CollectionLayout: UICollectionViewFlowLayout {

    private var width: Int = 1
    private var height: Int = 1

    func set(width: Int, height: Int) {
        guard width > 0, height > 0 else { return }

        self.height = height
        self.width = width
        calculateItemSize()
    }

    private func calculateItemSize() {
        guard let collectionView = collectionView else { return }
        let size = collectionView.frame.size
        var itemWidth = size.width / CGFloat(width)
        // spacing is needed only if there're more than 2 items in a row
        if width > 1 {
            itemWidth -= minimumInteritemSpacing
        }
        var itemHeight = size.height / CGFloat(height)
        if height > 1 {
            itemHeight -= minimumLineSpacing
        }
        let edgeLength = min(itemWidth, itemHeight)
        itemSize = CGSize(width: edgeLength, height: edgeLength)
    }

    // calculate origin for every item
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItem(at: indexPath)

        // calculate item position in the grid
        let col = CGFloat(indexPath.row % width)
        let row = CGFloat(Int(indexPath.row / width))

        // don't forget to take into account 'minimumInteritemSpacing' and 'minimumLineSpacing'
        let x = col * itemSize.width + col * minimumInteritemSpacing
        let y = row * itemSize.height + row * minimumLineSpacing

        // set new origin
        attributes?.frame.origin = CGPoint(x: x, y: y)

        return attributes
    }

    // accumulate all attributes
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
        var newAttributes = [UICollectionViewLayoutAttributes]()
        for attribute in attributes {
            if let newAttribute = layoutAttributesForItem(at: attribute.indexPath) {
                newAttributes.append(newAttribute)
            }
        }
        return newAttributes
    }
}

将我们的布局设置为UICollectionView

enter image description here

每次用户输入宽度和高度时更新集合视图:

@IBAction func onSetTapped(_ sender: Any) {
    width = Int(widthTextField.text!)
    height = Int(heightTextField.text!)
    if let width = width, let height = height,
       let layout = collectionView.collectionViewLayout as? CollectionLayout {
        layout.set(width: width, height: height)
        collectionView.reloadData()
    }
}