尽管实现了 sizeForItemAt,但 UICollectionViewFlowLayout 单元格大小永远不会改变

Posted

技术标签:

【中文标题】尽管实现了 sizeForItemAt,但 UICollectionViewFlowLayout 单元格大小永远不会改变【英文标题】:UICollectionViewFlowLayout cell size never changes despite sizeForItemAt implemented 【发布时间】:2017-12-19 20:43:25 【问题描述】:

我有一个UICollectionViewCell,里面有一个UICollectionView,里面有UICollectionViewFlowLayout。我正在使单元格成为集合视图的委托和数据源。我符合UICollectionViewDelegateFlowLayout 并实现collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize(并返回正确的大小)。

但是,当我调用 layout.itemSize.height 时,我得到默认高度 50。

class MyCell: UICollectionViewCell 
  fileprivate lazy var collectionView: UICollectionView = 
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(SomeOtherCell.self, forCellWithReuseIdentifier: SomeOtherCell.reuseIdentifier)
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.showsVerticalScrollIndicator = false
    return collectionView
  ()

  fileprivate lazy var layout: UICollectionViewFlowLayout? = 
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.scrollDirection = .horizontal
    layout?.sectionInset = UIEdgeInsets.zero
    return layout
  ()

  override func sizeThatFits(_ size: CGSize) -> CGSize 
  collectionView.frame = CGRect(x: layoutMargins.left, y: origin.y, width: width, height: layout.itemSize.height)
  // layout.itemSize.height is always 50
  

extension MyCell: UICollectionViewDelegateFlowLayout 
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
    guard tags.count != 0 else 
        return .zero
    

    let tag = Tag(text: tags[indexPath.item], index: indexPath.item)
    placeholderCell.configure(with: tag)
    let size = placeholderCell.getFrame(at: .zero, fitting: placeholderCell.width, alignedBy: semanticContentAttribute, apply: false).size
    collectionViewWidth += size.width
  // this size is always correct, that's what I want when I call  layout.itemSize.height 
    return size
  

【问题讨论】:

【参考方案1】:

问题

根据Apple documents for UICollectionViewFlowLayout's itemSize

如果delegate没有实现collectionView(_:layout:sizeForItemAt:)方法,流布局使用这个属性中的值来设置每个单元格的大小。这会导致所有单元格的大小都相同。

默认大小值为 (50.0, 50.0)。

它明确表示itemSize 仅在委托未实现 collectionView(_:layout:sizeForItemAt:) 方法并且没有提及itemSize 将从collectionView(_:layout:sizeForItemAt:) 返回值时使用。 它们是 2 个不同的值。这就是为什么 itemSize 不会改变,尽管 sizeForItemAt 已实现

因此,如果layout.itemSize.height 在您使用时具有默认值 (50),则它是有意义的。

您的问题有什么解决方案?

如果collectionView 上的所有单元格大小相同,我建议为itemSize 设置一个值,并且不要实现collectionView(_:layout:sizeForItemAt:) 方法(删除UICollectionViewDelegateFlowLayout 的扩展名)。

class MyCell: UICollectionViewCell 
  fileprivate lazy var collectionView: UICollectionView = 
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    // collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(SomeOtherCell.self, forCellWithReuseIdentifier: SomeOtherCell.reuseIdentifier)
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.showsVerticalScrollIndicator = false
    return collectionView
  ()

  fileprivate lazy var layout: UICollectionViewFlowLayout? = 
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.scrollDirection = .horizontal
    layout?.sectionInset = UIEdgeInsets.zero
    // Calculate and change |itemSize| here
    layout?.itemSize = YOUR_CELL_SIZE_AFTER_CALCULATED
    return layout
  ()

  override func sizeThatFits(_ size: CGSize) -> CGSize 
    collectionView.frame = CGRect(x: layoutMargins.left, y: origin.y, width: width, height: layout.itemSize.height)
    // layout.itemSize.height is |YOUR_CELL_SIZE_AFTER_CALCULATED.height|
  

如果collectionView 的每个单元格大小不同,则需要使用UICollectionView's layoutAttributesForItem(at:) method。在 IndexPath 处获取 UICollectionViewLayoutAttributes 并使用 layoutAttributes.size 计算

layoutAttributes.size 是从collectionView(_:layout:sizeForItemAt:) 返回的值

let indexPath = // IndexPath you need to use to calculate
let layoutAttributes : UICollectionViewLayoutAttributes? = collectionView.layoutAttributesForItem(at: indexPath)
//  Use this value to calculate |collectionView| frame
print(layoutAttributes?.size)

【讨论】:

【参考方案2】:

我认为您的注册问题...删除它并在情节提要中设置单元格。只是一个疯狂的猜测......

您有 2 个单元格,其中一个已为 collectionView 注册?

如果你在storyboard中设置单元格,注册也可能会导致问题

  collectionView.register(SomeOtherCell.self, forCellWithReuseIdentifier: SomeOtherCell.reuseIdentifier)

【讨论】:

以上是关于尽管实现了 sizeForItemAt,但 UICollectionViewFlowLayout 单元格大小永远不会改变的主要内容,如果未能解决你的问题,请参考以下文章

滚动时未调用 sizeForItemAt

CollectionView SizeForItemAt 基于 Cell 类?

访问 layout.collectionViewContentSize 时 collectionView(_:layout:sizeForItemAt:) 中的异常

如何将 sizeForItemAt 放在后台线程上?

UICollectionViewCell 在 sizeForItemAt 中不可用

qmake 不调用 uic