CollectionView has Weird Behavior

Hi Apple Dev Community,
I have a single ViewController, in which I have a (custom) CollectionView, in which I have 64 (custom) CollectionViewCells, each in which I have a single UILabel and UIImageView.
My goal is to populate the CollectionView with CollectionViewCells, with a common background image and a label (in this example, the label is simple the indexPath.item)

Straightfoward enough....


or you would think. I'm encountering weird behavior where only the first cell gets a label! To make matters worse, none of the cells are getting an image!

I figured the background color might be the culprint, so I commented the line out....and all of a sudden, the 8x8 grid turns into a 4x4 grid!
WHAT THE **** IS GOING ON!?? I've never seen any behavior like this....below is the code:
ViewController.swift

import UIKit

class ViewController: UIViewController{
    
    var collectionView: collectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView = newCollectionView()
    }
    
    func newCollectionView() -> CollectionView {
        let collectionView = CollectionView(viewController: self)
        self.view.addSubview(collectionView)
        collectionView.activateConstraints()
        return collectionView
    }
    
}


CollectionView.swift

import UIKit

class CollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
    
    var viewController: ViewController!
    
    init(viewController: ViewController) {
        self.viewController = viewController
        let width = viewController.view.frame.width - 20
        let frame = CGRect(x: 0, y: (viewController.view.frame.height - width)/2, width: width, height: width)
        let flowLayout = FlowLayout()
        super.init(frame: frame, collectionViewLayout: flowLayout)
        self.delegate = self
        self.dataSource = self
        self.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        self.backgroundColor = UIColor.red
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    func activateConstraints() {
        let view = viewController.view!
        translatesAutoresizingMaskIntoConstraints = false
        centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true        widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
        heightAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
        contentInsetAdjustmentBehavior = .always
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 64
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath) as! CollectionViewCell
        cell.label.text = "\(indexPath.item)"
        cell.backgroundColor = UIColor.gray // Comment this line out and the 8x8 grid turns into a 4x4 grid!
        return cell
    }
    
}

FlowLayout.swift

import UIKit

class FlowLayout: UICollectionViewFlowLayout {
    
    var gridSize: Int = 8
    
    override init() {
        super.init()
        self.minimumInteritemSpacing = 1
        self.minimumLineSpacing = 1
        self.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 0, right: 10)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func prepare() {
        super.prepare()
        // Make sure the collection view is valid
        guard let collectionView = collectionView else { return }
        let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(gridSize - 1)
        // Make item width as large as possible without bleeding
        let cellSize = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(gridSize)).rounded(.down)
        itemSize = CGSize(width: cellSize, height: cellSize)
    }
    
    override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
        let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
        context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
        return context
    }
    
}

Replies

I must be missing something in your logic.


You have a ViewController: UIViewController


in which you insert a CollectView as a subview

Note: I do not understand how ithe following compiles:

var collectionView: collectionView!

as collectionView is not a class (it seems to be CollectionView)


Why do store a reference to its ViewController ? If you need to access it, just call clllectionView.parent.


Most critical is the following:

    init(viewController: ViewController) { 
        self.viewController = viewController 
        let width = viewController.view.frame.width - 20 
        let frame = CGRect(x: 0, y: (viewController.view.frame.height - width)/2, width: width, height: width) 
        let flowLayout = FlowLayout() 
        super.init(frame: frame, collectionViewLayout: flowLayout) 
        self.delegate = self 
        self.dataSource = self 
        self.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") 
        self.backgroundColor = UIColor.red 
    }


Line 7 and 8, you set the delegate for the CollectionView to itself. It should be set to the ViewController, as self if the collectionView


        self.delegate = viewController
        self.dataSource = viewController

"Why do store a reference to its ViewController ? If you need to access it, just call clllectionView.parent."
Negative. That doesn't work as I don't have access to "collectionView".
When I try to call collectionView.parent, I get the following error message:
Use of unresolved identifier 'collectionView';

Where did you try to access collectionView.parent ?


ANyway, that's not the most important point. Have you looked how delegates are set ?

I tried to access collectionView.parent from within CollectionView.swift (which doesn't work). Any ideas why this isn't working?
Yes, I've updates the delegates. They are now:

self.delegate = viewController
self.dataSource = viewController

Unfortunately, even with your suggestions implemented, the behavior is unchanged. Any further thoughts?
Here is my updates code:
(FlowLayout.swift is the same as before)
ViewController.swift

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    
    var gridCollectionView: GridCollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        gridCollectionView = newCollectionView()
    }
    
    func newCollectionView() -> GridCollectionView {
        let gridCollectionView = GridCollectionView(viewController: self)
        self.view.addSubview(gridCollectionView)
        gridCollectionView.activateConstraints()
        return gridCollectionView
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 64
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath) as! CollectionViewCell
        cell.label.text = "\(indexPath.item)"
        cell.backgroundColor = UIColor.gray // Comment this line out and the 8x8 grid turns into a 4x4 grid!
        return cell
    }
    
}

CollectionView.swift

import UIKit

class GridCollectionView: UICollectionView {
    
    var viewController: ViewController!
    
    init(viewController: ViewController) {
        self.viewController = viewController
        let width = viewController.view.frame.width - 20
        let frame = CGRect(x: 0, y: (viewController.view.frame.height - width)/2, width: width, height: width)
        let gridLayout = GridFlowLayout()
        super.init(frame: frame, collectionViewLayout: gridLayout)
        self.delegate = viewController
        self.dataSource = viewController
        self.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        self.backgroundColor = UIColor.red
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    func activateConstraints() {
        let view = viewController.view!
        translatesAutoresizingMaskIntoConstraints = false
        centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
      NSLayoutConstraint(item: self, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: -50).isActive = true
        widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
        heightAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
        contentInsetAdjustmentBehavior = .always
    }
    
}

Again, any help is greately appreciated!

You did not post the CollectionViewCell class.

Could you do it ?

Your implementation of UICollectionView is far from Straightfoward and may cause bunch of unexpected behaviors...


But all these things may happen when your CollectionViewCell is not implemented properly.

- only the first cell gets a label

- none of the cells are getting an image

- commented the line out....and all of a sudden, the 8x8 grid turns into a 4x4 grid


Please show enough code to reproduce the issue.

Hey thank you Claude31 and OOper!
Your advice/suggestions pointed me in the right direction.
Turns out the weird behavior was not due to a single underlying bug, but a combination of multiple smaller bugs.
Everything is fixed and working now. Thanks!

Great. Good continuation.


Please, don't forget to mark the thread as closed.