UICollectiionView Weirdness

Hi All - First time posting, hope I'm not breaking too many rules or protocols.


I'm trying to gain more experience with Collection Views to replace a Table View in one of my apps - it looks so simple, but I'm runnng into problem after problem. Most I can solve, but this one completely eludes me. It seems like it would be pretty straight forward, but apparently not.


UICollectionView in a UIView, UICollectionViewCell - main.storyboard view & simulator view

(Well, I actually pasted an image large enough to be easily viewable, but it appears, that's not an option. So here's a Dropbox link:

https://www.dropbox.com/s/34fvfhzipo0q00l/ScreenCaptures%4050%25.jpg?dl=0 )


The app appears to start off working normally and as expected, then after scrolling past cell #11 (of 25), it seems to completely lose its layout as you can see in the right side of the above graphic. Scrolling upward, it's even more wonky, all the cells turn into a full size image.


All cell insets and such are defined as zero as to not contribute to the problem.


During this "weirdness" the debugger begins pumping out message after message:

My debug messages for cells 11 & 12 from "override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {)" seem consistent.


// My debug messages ... for cells 11, 12, & 13

indexPath.item: 11

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, 169.66666666666666, 414.0, 736.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

layoutAttributesForItem: Optional(<UICollectionViewLayoutAttributes: 0x7fe495315500> index path: (<NSIndexPath: 0x8fc82db6ba53fbc9> {length = 2, path = 0 - 11}); frame = (214 968; 192 192); )

indexPath.item: 12

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, 355.0, 414.0, 736.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

layoutAttributesForItem: Optional(<UICollectionViewLayoutAttributes: 0x7fe495309740> index path: (<NSIndexPath: 0x8fc82db6bab3fbc9> {length = 2, path = 0 - 12}); frame = (8 1160; 192 192); )

indexPath.item: 13

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, 355.0, 414.0, 736.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

layoutAttributesForItem: Optional(<UICollectionViewLayoutAttributes: 0x7fe495302230> index path: (<NSIndexPath: 0x8fc82db6ba93fbc9> {length = 2, path = 0 - 13}); frame = (214 1160; 192 192); )


// Debugger Messages when things start going downhill ...

2019-10-22 01:32:54.794032-0500 CollectionTest#1[13173:339022] The behavior of the UICollectionViewFlowLayout is not defined because:

2019-10-22 01:32:54.794203-0500 CollectionTest#1[13173:339022] the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.

2019-10-22 01:32:54.794360-0500 CollectionTest#1[13173:339022] Please check the values returned by the delegate.

2019-10-22 01:32:54.794856-0500 CollectionTest#1[13173:339022] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fe4c1407750>, and it is attached to <UICollectionView: 0x7fe492819400; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = LM+W+RM+TM+H+BM; gestureRecognizers = <NSArray: 0x600000965050>; layer = <CALayer: 0x60000076efe0>; contentOffset: {0, 355}; contentSize: {414, 2512}; adjustedContentInset: {20, 0, 0, 0}> collection view layout: <UICollectionViewFlowLayout: 0x7fe4c1407750>.

2019-10-22 01:32:54.801289-0500 CollectionTest#1[13173:339022] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

This just goes on and on ... abbreviated for brevity.


// My debug messages from "func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {)" seem to be consistent with all the cells before and after


width[10]: 414.0

cellDimension[10]: 192.0

returnSize[10]: (192.0, 192.0)

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, -20.0, 414.0, 736.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

width[11]: 414.0

cellDimension[11]: 192.0

returnSize[11]: (192.0, 192.0)

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, -20.0, 414.0, 736.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

width[12]: 414.0

cellDimension[12]: 192.0

returnSize[12]: (192.0, 192.0)

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, -20.0, 414.0, 736.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

width[13]: 414.0

cellDimension[13]: 192.0

returnSize[13]: (192.0, 192.0)

view.bounds: (0.0, 0.0, 414.0, 736.0)

collectionView.bounds: (0.0, -20.0, 414.0, 736.0)

collectionView.alignmentRectInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

collectionView.contentInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

width[14]: 414.0


I've modeled my code after the best examples I've been able to find, and thought I had solved the constraints problems - I think that's what's causing the problems, but no matter how I define the constraints, even in a stack view, this seems to always happen.


The code is pretty straight forward, so I'll save you some reading and only post what I think is the most suspect, if addition code is required, I'm happy to post it.


//Maybe should have picked a different name for my CollectionViewCell class, but it is descriptive.


CollectionViewCell.swift


import UIKit


class CollectionViewCell: UICollectionViewCell {

@IBOutlet weak var cellImage: UIImageView!

@IBOutlet weak var cellLabel: UILabel!


}


ViewController.swift


import UIKit


class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {


override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

return 25

}


override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell

cell.cellImage.image = UIImage(named: "file-35")

cell.cellLabel.text = "cell# \(indexPath.item)"


cell.layer.borderWidth = 0.5

cell.layer.borderColor = UIColor.lightGray.cgColor

cell.layer.cornerRadius = 10


cell.backgroundColor = UIColor.black

cell.cellImage.layer.cornerRadius = 10


print("indexPath.item: \(indexPath.item)")

print("view.bounds: \(view.bounds)")

print("collectionView.bounds: \(collectionView.bounds)")

print("collectionView.contentInset: \(collectionView.contentInset)")

print("collectionView.alignmentRectInsets: \(collectionView.alignmentRectInsets)")


print("layoutAttributesForItem: \(String(describing: collectionView.layoutAttributesForItem(at: indexPath)))")


return cell

}



override func viewDidLoad() {

super.viewDidLoad()

// Do any additional setup after loading the view.

collectionView.delegate = self

collectionView.dataSource = self

// view.translatesAutoresizingMaskIntoConstraints = true

}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

if indexPath.item > 10 {

print("view.bounds: \(view.bounds)")

print("collectionView.bounds: \(collectionView.bounds)")

// collectionView.bounds = view.bounds

print("collectionView.alignmentRectInsets: \(collectionView.alignmentRectInsets)")

print("collectionView.contentInset: \(collectionView.contentInset)")

}

let width = view.bounds.width

print("width[\(indexPath.item)]: \(width)")

let cellDimension = (width / 2 ) - 15

print("cellDimension[\(indexPath.item)]: \(cellDimension)")

let returnSize = CGSize(width: cellDimension, height: cellDimension)

print("returnSize[\(indexPath.item)]: \(returnSize)")

return returnSize

}


}


It just seems so "odd" that the 1st screen of cell display as expected, then when it starts reusing cells, it's lost all its layout.


Thank you in advance for any solutions.

Would need time and full poject to test completely.


How do label show in cells 11 and next ?

Are the height of those cells OK ?

Same 2 questions when you scroll back up.


I look the constraints you have defined:

- you set constraints between label and image.

- you should probably set the constraints relative to the cell itself


But it seems you want all the cells to have the same size, exact ?

If so, for a quick fix, remove the func sizeItemAt and just set itemSize ot the layout.


Look here.

https://stackoverflow.com/questions/31662155/how-to-change-uicollectionviewcell-size-programmatically-in-swift

Claude31 - Thanks for the reference to the Stack Overflow article. Some of it was helpful and pushed me into a direction that seems to have helped solve the problems.


I could be worng, but it seems that not all Cells get their Size initialized from the Storyboard values. The first dozen or so do but when the collection view starts reusing cells, it loses track of the cell size.


Watching with the debugger, sizeForItemAt() only gets called initially, not when cells start being reused, and when the size the cell size has been lost, all sorts fo undesirable things begin to happen.


Initializing the cell size in viewDidLoad() eliminated most of the problems, that is until the device is rotated, then sizeForItemAt() gets called in iPhone simulators, but not iPad simulators. sizeForItemAt() seems to get called on all the iPhone simulators I tested, but not with any of the iPad simulators.


The solution to this is to check for the device type in viewDidLoad() and compute the desired number of cells per row. Simply setting the cell size of the layout doesn't help if you want a different number of cell per row when the device is rotated.


This seems to have tamed the problems, I'm just a bit uncomfortable that the sizeForItemAt() function doesn't get called as expected.

Good to see it works, but effectively that's not totally OK.


Have a look here for further hints.


https://stackoverflow.com/questions/39107482/collectionview-sizeforitematindexpath-never-called

Thank you for the feedback. I would really appreciate learning the details about what you mean by "that's not totally OK"


I've reviewed the articles you provided links for and they mostly say "You need to specify that you implement the protocol

UICollectionViewDelegateFlowLayout
in your class declaration." You can clearly see in my included source code, that the protocol is implemented.


Subsequent answers were to make sure the delegate was assigned - "

collectionView.delegate = self"

which is clearly is in my code. and that sizeForItemAt() should be implemented and handled - it is implemented, and is handled.


So, I'm apparetnly missing what is "not totally OK" - especially when my updated code now displays cells as desired and expected, supports device rotation which neither of the referenced articles address, doesn't spew out any debug messages, and doesn't appear to leak memory.


Please be specific.

What i meant by "is not totally OK" was referring to your own comment " I'm just a bit uncomfortable that the sizeForItemAt() function doesn't get called as expected."

Sorry if I hurt you.

It works, that's important.


Having to "check for the device type in viewDidLoad() and compute the desired number of cells per row" is something that should be done automatically.


Have you implemented a func viewWillLayout ?

This is called when you rotate, so that could be a place to do ask to refresh layout:


override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews() 
  myCollection.collectionViewLayout.invalidateLayout()
}


Have a look here for further explanation

https://stackoverflow.com/questions/36884376/swift-how-to-refresh-uicollectionview-layout-after-rotation-of-the-device

UICollectiionView Weirdness
 
 
Q