ios - 使用自定义布局的 UICollectionViewCell 高度

标签 ios swift layout collections view

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

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

func heightForItem(_ collectionView: UICollectionView, at indexPath: IndexPath) -> CGFloat 

在我的 View Controller 中实现的自定义布局委托(delegate)协议(protocol)中的方法。

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

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

自定义布局协议(protocol):

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

以及协议(protocol)的实现(来自 View Controller ):

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

最佳答案

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

因为我们有具体单元格的 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
        }
    }

关于ios - 使用自定义布局的 UICollectionViewCell 高度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45846260/

相关文章:

swift - 如何在 Swift 中为文件访问指定备用目录

eclipse - 重置 Eclipse 透视布局

ios - 设置当前位置标题和副标题 - Google Maps SDK IOS

ios - 使用UISlider旋转图像?

ios - 在 iOS 中使用 AVPlayer 进行音频流播放时的播放速度

arrays - 按某个键值对字典数组进行排序(Swift 3)

ios - 系统.ExecutionEngineException : Attempting to JIT compile method only in Debug Mode on device (MonoTouch)

ios - 如何处理从另一个应用程序发送到我自己的 iOS 应用程序的 'Open in...' 文件?

java - 如何从列表适配器刷新布局/上下文(我认为这就是我需要的)

html - 如何判断 HTML 元素是否离开了页面底部?