UICollectionViewCell使用自定义布局的高度

时间:2017-08-23 17:45:06

标签: ios swift layout collections view

我在UICollectionView自定义布局实现期间遇到了问题。

问题是我需要在自定义布局prepare()中计算集合视图单元格的高度。为此,我有:

func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat 
自定义布局的委托协议中的

方法,它在我的视图控制器中实现。

然而,这个方法在单元格出列之前被调用,并且实际上有任何数据可以根据我的数据来计算它的高度。因此,如果单元格的内容超出了它的初始范围 - 我无法看到部分内容。

有没有人遇到过自定义布局的同样问题?你是怎么解决的?

自定义布局协议:

protocol CustomLayoutDelegateProtocol: class {
    func numberOfSectionsInRow() -> Int
    func indecesOfSectionsInRow() -> [Int]
    func minimumInteritemSpace() -> CGFloat
    func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
}

自定义布局本身:

class CustomLayoutClass: UICollectionViewLayout {

    weak var delegate: CustomLayoutDelegateProtocol? {
        didSet {
            setupLayout()
        }
    }

    private var cache = [UICollectionViewLayoutAttributes]()
    private var contentHeight: CGFloat = 0.0
    private var contentWidth: CGFloat {
        guard let collectionView = collectionView else { return 0 }
        return collectionView.bounds.width
    }
    private var interitemSpace: CGFloat?
    private var numberOfColumns: Int?
    private var columnedSections: [Int]?


    override init() {
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private func setupLayout() {
        numberOfColumns = delegate?.numberOfSectionsInRow()
        columnedSections = delegate?.indecesOfSectionsInRow()
        interitemSpace = delegate?.minimumInteritemSpace()
    }

    override func invalidateLayout() {
        cache.removeAll()
        super.invalidateLayout()
    }

    override func prepare() {
        if cache.isEmpty {
            guard let collectionView = collectionView,
                  let numberOfColumns = numberOfColumns,
                  let columnedSections = columnedSections,
                  let interitemSpace = interitemSpace else { return }

            let columnWidth = (contentWidth / CGFloat(numberOfColumns))
            var xOffset = [CGFloat]()
            for column in 0..<numberOfColumns {
                var interitemSpace = interitemSpace
                if column == 0 { interitemSpace = 0 }
                xOffset.append(CGFloat(column) * columnWidth + interitemSpace)
            }

            var yOffset: CGFloat = 0.0

            for section in 0..<collectionView.numberOfSections {
                for item in 0..<collectionView.numberOfItems(inSection: section) {
                    let indexPath = IndexPath(item: item, section: section)
                    guard let sectionInsets = delegate?.collectionView(collectionView, layout: self, insetForSectionAt: indexPath.section),
                          let height = delegate?.heightForItem(collectionView, at: indexPath) else { continue }
                    let width = columnedSections.contains(section) ? columnWidth : contentWidth

                    let xOffsetIdx = columnedSections.contains(section) ? columnedSections.index(of: section)! % numberOfColumns : 0

                    yOffset += sectionInsets.top
                    let frame = CGRect(x: xOffset[xOffsetIdx], y: yOffset, width: width, height: height)

                    let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                    attributes.frame = frame
                    cache.append(attributes)

                    contentHeight = max(contentHeight, frame.maxY)

                    let isLastInRow = (columnedSections.contains(section) && columnedSections.index(of: section)! % numberOfColumns == (numberOfColumns-1))
                    let isNotColumnedSection = !columnedSections.contains(section)

                    if isLastInRow || isNotColumnedSection {
                        yOffset += height + sectionInsets.bottom
                    }
                }
            }
        }
    }

    override var collectionViewContentSize: CGSize {
        return CGSize(width: contentWidth, height: contentHeight)
    }

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

        for attributes in cache {
            if attributes.frame.intersects(rect) {
                layoutAttributes.append(attributes)
            }
        }
        return layoutAttributes
    }
}

协议的实现(来自视图控制器):

extension ViewController: CustomLayoutDelegateProtocol {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    guard let sectionType = InvoiceSectionIndexType(rawValue: section) else { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) }

    switch sectionType {
    case .receiver:
        return UIEdgeInsets(top: Constants.bigLineSpace, left: 0, bottom: Constants.bigLineSpace, right: 0)

    default:
        return UIEdgeInsets(top: 0, left: 0, bottom: Constants.commonLineSpace, right: 0)
    }
}

func numberOfSectionsInRow() -> Int {
    return Constants.numberOfSectionsInRow
}

func indecesOfSectionsInRow() -> [Int] {
    return [0, 1]
}

func minimumInteritemSpace() -> CGFloat {
    return Constants.interitemSpace
}

func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat {
    guard let sectionType = InvoiceSectionIndexType(rawValue: indexPath.section) else { return 0 }

    switch sectionType {
    case .next:
        return Constants.nextButtonHeight

    default:
        return Constants.estimatedRowHeight
    }
}
}

1 个答案:

答案 0 :(得分:0)

我自己解决了这个问题,解决方案虽然不是很明显但很简单。

由于我们有具体单元格的indexPath,我们可以找到我们的内容(在我的例子中,内容是放入标签的简单文本)。然后我们可以创建标签(我们不会显示,因为它只需要计算),宽度(在我的情况下我知道单元格的宽度)和高度CGFloat.greatestFiniteMagnitude。然后我们将sizeToFit()应用于我们的标签,现在我们有适当高度的标签。我们现在应该做的唯一事情 - 将这个新高度应用到我们的细胞中。

示例代码:

func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {
        // ask your DataSource for piece of data with indexPath

        let testLabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
        testLabel.text = // your text
        testLabel.sizeToFit()

        if testLabel.bounds.height > Constants.defaultContentLabelHeight {
            return Constants.estimatedRowHeight + (testLabel.bounds.height - Constants.defaultContentLabelHeight)
        } else {
            return Constants.estimatedRowHeight
        }
    }