我在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
}
}
}
答案 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
}
}