Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionViewFlowLayout cell size never changes despite sizeForItemAt implemented

I have a UICollectionViewCell that has a UICollectionView with UICollectionViewFlowLayout inside. I am making the cell the delegate and the datasource of the collection view. I am conforming to UICollectionViewDelegateFlowLayout and implementing collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize (and returning the correct size).

However, when I call layout.itemSize.height I get the default 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
  }
}
like image 819
nambatee Avatar asked Dec 19 '17 20:12

nambatee


People also ask

What is UICollectionViewFlowLayout?

A layout object that organizes items into a grid with optional header and footer views for each section.

How do you give height and width to collection VIEW cell in Swift?

class MyLayout: UICollectionViewFlowLayout { override func prepare() { super. prepare() guard let collectionView = collectionView else { return } itemSize = CGSize(width: ..., height: ...) } }

What is compositional layout in Swift?

A compositional layout is a type of collection view layout. It's designed to be composable, flexible, and fast, letting you build any kind of visual arrangement for your content by combining — or compositing — each smaller component into a full layout.


1 Answers

Problem

  • According to Apple documents for UICollectionViewFlowLayout's itemSize

    If the delegate does not implement the collectionView(_:layout:sizeForItemAt:) method, the flow layout uses the value in this property to set the size of each cell. This results in cells that all have the same size.

    The default size value is (50.0, 50.0).

  • It says clearly that itemSize is only used when the delegate does not implement the collectionView(_:layout:sizeForItemAt:) method and no words mention that itemSize will return value from collectionView(_:layout:sizeForItemAt:). They are 2 different values. That's why itemSize won't change despite sizeForItemAt is implemented

    So it makes sense if layout.itemSize.height has default value (50) when you use it.

What is the solution for your problem?

  • If all of cells on your collectionView have same size, I suggest to set a value for itemSize and don't implement collectionView(_:layout:sizeForItemAt:) method (Remove extension for 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|
      }
    }
    
  • If collectionView has different size for each cell, you need to use UICollectionView's layoutAttributesForItem(at:) method. Get UICollectionViewLayoutAttributes at an IndexPath and use layoutAttributes.size to calculate

    layoutAttributes.size is value returned from 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)
    
like image 107
trungduc Avatar answered Oct 06 '22 01:10

trungduc