Monday, April 17, 2017

Weird bug returning a CGSize in UICollectionView sizeForItemAtIndexPath

Leave a Comment

I have variable widths in my UICollectionView and in my sizeForItemAtIndexPath function, I ultimately return a CGSize

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {      var width: CGFloat = indexPath.row == 0 ? 37 : 20 // default paddings based on spacing in cell     let font = UIFont.systemFont(ofSize: 14, weight: UIFontWeightRegular)      if indexPath.row == 0 {         width += listingsFilter.stringValue.width(withConstrainedHeight: 28, font: font)     } else {         let string = viewModel.valueRangeForButton[indexPath.row - 1]         width += string.width(withConstrainedHeight: 28, font: font)     }      print(width) // some decimal e.g. 138.1239581     let w: CGFloat = width.rounded() // rounded e.g. 13      return CGSize(width: w, height: 28) } 

If I stub the return value with a number for the width, say 128, rather than have a variable, the collection view respects UIEdgeInsets. If I return the CGSize with a variable for the width, UIEdgeInsets gets ignored for every element but the first and the last.

I feel like I found some deep CoreGraphics bug.

For the last two lines, if I change them to

let x: CGFloat = 128 // or 128.0, doesn't matter return CGSize(width: x, height: 28) 

It still doesn't work. I've tried returning a CGSize with the Int initializer. I've tried casting to Ints and back to CGFloats. Nothing seems to work.

I've narrowed down the problem to this point. It doesn't have anything to do with the width code above it (which is a string extension) or anything else.

Super weird. Anyone have experience with this?

Edit: Some images

Top one is with CGSize(width: w, height: 28) where w is a CGFloat that could equal 128 or whatever other value. Bottom one is CGSize(width: 128, height: 28)

enter image description here

enter image description here

5 Answers

Answers 1

I had similar issue few months ago. As far as I remember it was really annoying trying to fix this (as in your case) but ridiculously simple.
You need to make sure that your class inherits from UICollectionViewDelegateFlowLayout. It inherits from UICollectionViewDelegate. That should make the trick.
Let me know if that works for you.

Answers 2

As i can understand from op's question

to move content within cell you need

func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout:     UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {      return UIEdgeInsets(top: 15, left: 0, bottom: 15, right: 0) } 

to move spaces between respective cells you need

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {     return 15 } 

be sure that you confront the delegate UICollectionViewDelegateFlowLayout

Answers 3

Having never even tried to return a hardcoded value instead of a variable from sizeForItemAtIndexPath(), I've always assumed that UIEdgeInsets weren't even supposed to affect the spacing between the cells of a UICollectionView, but only the spaces surrounding an entire section of cells. Looks like UIEdgeInsets is not intended to affect the spacing between the cells, in which case Apple's bug is the reverse of what it seems to be -- that UIEdgeInsets is oddly affecting the spacing between cells in one outlier case, when in fact it was never intended to do so in any scenario. This red herring had you reasonably assume that there must be a reliable and programmatic way to get UIEdgeInsets to set the spacing between UICollectionView cells. I have yet to find a programmatic way to do so. When I found the following storyboard solution I stopped looking for one.

After having spent a great deal of time tweaking UICollectionViews to get them to look the way I want, I have found that the only reliable way to set the spacing between their cells is to use storyboard settings as follows:

In the storyboard, select your UICollectionView, go to the attributes inspector, find the "Min Spacing" section, and type in the "For Cells" box the value you'd like (in your case, 10). The "For Cells" value is the horizontal spacing between cells. Do the same for the "For Lines" value, which is the vertical spacing between cells.

Answers 4

Please check the code below,

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {  override func viewDidLoad() {     super.viewDidLoad()     // Do any additional setup after loading the view, typically from a nib.     collectionView?.delegate = self     collectionView?.contentInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15) }  override func didReceiveMemoryWarning() {     super.didReceiveMemoryWarning()     // Dispose of any resources that can be recreated. }    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {      let x: CGFloat = 50 + CGFloat(arc4random_uniform(50)) // or 128.0, doesn't matter     return CGSize(width: x, height: 28) }  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {     return UIEdgeInsets(top: 15, left: 0, bottom: 15, right: 0) }  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {     return 15 }   override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {     return 50 }   override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {     return collectionView.dequeueReusableCell(withReuseIdentifier: "aaa", for: indexPath) }   override func numberOfSections(in collectionView: UICollectionView) -> Int {     return 1 } 

}

Result is enter image description here

Answers 5

It's not related to UIEdgeInsets, it relates to width that you're calculating in code. When you set width: 128, the width provided for your cells are bigger than what they actually need (let's say 90), hence depending on your UICollectionViewCell's design, it might feel there is an UIEdgeInsets for each cell.

To solve your problem:

If you're using IB, you can set cellSpacing and lineSpacing from IB, other-wise you have to implement UICollectionViewDelegateFlowLayout

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment