Label I Added in Storyboard Appears nil when I try to Dequeue a Cell Using a Data Source Cell Provider

I have added a UICollectionViewCell to my storyboard, and I added a UILabel to my UICollectionViewCell in storyboard. I have created a cell registration using UICollectionView.CellRegistration and have implemented the cellProvider closure for the datasource which dequeue a collection view cell of type TapLabelCollectionViewCell(I have subclassed the cell in my storyboard to this class).

In my TapLabelCollectionViewCell, I am trying to set the tap gesture recogniser on the label, but the label appears nil, which I've connected using an @IBOutlet. Why is this and how can I fix it?

My code :

// UI View Controller:

class TapGridViewController: UIViewController {
   override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        tapGridCollectionView.collectionViewLayout = createLayout()
        
        configureDataSource()
        
        applySnapshot()
    }

    func configureDataSource() {
        
        let cellRegistration = UICollectionView.CellRegistration<TapLabelCollectionViewCell, CellItem>(handler: {
            (cell: TapLabelCollectionViewCell, indexPath: IndexPath, item: CellItem) in
            
            cell.taplabel.text = String(item.labelCount)
        })
        
        dataSource = UICollectionViewDiffableDataSource(collectionView: tapGridCollectionView, cellProvider: { (collectionView: UICollectionView, indexPath: IndexPath, item: CellItem) in
            
            let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
            
            cell.delegate = self
            
            cell.index = indexPath.row
            
            return cell
        })
    }
}

// UI Collection View Cell:

protocol TapLabelCollectionViewCellDelegate: AnyObject {
    func incrementNumberOfTaps(index: Int)
}

class TapLabelCollectionViewCell: UICollectionViewCell {
    @IBOutlet var taplabel: UILabel!
    
    var delegate: TapLabelCollectionViewCellDelegate?
        
    var index: Int!
    
    static let identifier = "tapLabelCellIdentifier"
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUpTapGestureRecognizer()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    func setUpTapGestureRecognizer() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(incrementNumberOfTaps))
        print("tap Label,", taplabel)
        taplabel.addGestureRecognizer(tapGestureRecognizer)
    }
    
    @objc func incrementNumberOfTaps() {
        delegate?.incrementNumberOfTaps(index: index)
    }
}

In TapGridViewController, you have:

cell.delegate = self

delegate is a TapGridViewController

In TapLabelCollectionViewCell

you declare

    var delegate: TapLabelCollectionViewCellDelegate?

But TapLabelCollectionViewCell does not conform to TapLabelCollectionViewCellDelegate

Why does TapLabelCollectionViewCell need to conform to TapLabelCollectionViewCellDelegate? Isn't it the delegate who needs to conform to this protocol?

The delegate is of type TapLabelCollectionViewCellDelegate

So cell.delegate is of type TapLabelCollectionViewCellDelegate

So unless I miss something in your code, self (which is TapGridViewController) must conform as well.

Another point: Why don't you put

           cell.delegate = self
           cell.index = indexPath.row

in CellRegistration handler ?

There is an extra part in my code:

extension TapGridViewController: TapLabelCollectionViewCellDelegate {
    func incrementNumberOfTaps(index: Int) {
        cellItems[index].incrementLabelCount()
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, CellItem>()
         
        snapshot.appendItems(cellItems)
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

I also moved the cell.delegate = self and the cell.index = indexPath.row to the Cell Registration handler, initially I thought I wasn't able to capture self in the cell registration handler, but upon checking again I can.

A test project would be super helpful in investigating what's going on here.

At first parsing:

  • It could be that the label isn't connected in Interface Builder properly

  • You should add a breakpoint at line 53 setUpTapGestureRecognizer

  • Can you verify that awakeFromNib() is called since your cell is loaded from the storyboard? you'll want to call setUpTapGestureRecognizer after awakeFromNib

Hello,

The steps I've gone through to add the label to the Interface Builder are as follows:

  1. I dragged the label to my UICollectionViewCell in storyboard.

This is the outlet connection that is appearing in the identity inspector:

I have added a breakpoint at line 53 setUpTapGestureRecognizer. This breakpoint gets called while the breakpoint at awakeFromNib() does not get called. I've also verified that the outlet in the code is connected to the label because the circle appears filled.

Link to project: link.

There seems to be some confusion in your code.

In the cell, you call

delegate?.incrementNumberOfTaps(index: index)

But you don't show where is this func implementation.

It should be in

class TapGridViewController: UIViewController, TapLabelCollectionViewCellDelegate

as delegate is

cell.delegate = self

self is an instance of TapGridViewController

I have implemented the incrementNumberOfTaps(index:) method in the extension of the TapGridViewController:

extension TapGridViewController: TapLabelCollectionViewCellDelegate {
    func incrementNumberOfTaps(index: Int) {
        
        cellItems[index].incrementLabelCount()
        
        cellItems.append(CellItem(numberOfTaps: 0))
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, CellItem>()
         
        snapshot.appendSections([.main])
        
        snapshot.appendItems(cellItems)
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

And the delegate is assigned in the TapGridViewController as well in the cell registration method:

class TapGridViewController: UIViewController { 
    func configureDataSource() {
        
        let cellRegistration = UICollectionView.CellRegistration<TapLabelCollectionViewCell, CellItem>(handler: { (cell: TapLabelCollectionViewCell, indexPath: IndexPath, item: CellItem) in
            
            cell.delegate = self //assigning the delegate of the cell here.
            
            cell.index = indexPath.row
            
            cell.numberOfTapsLabel.text = String(item.numberOfTaps)
        })

}

I have fixed this issue by removing the label from the storyboard and adding programmatically like so:

protocol TapLabelCollectionViewCellDelegate: AnyObject {
    func incrementNumberOfTaps(index: Int)
}

class TapLabelCollectionViewCell: UICollectionViewCell {

    var numberOfTapsLabel: UILabel!
    
    var delegate: TapLabelCollectionViewCellDelegate?
    
    var index: Int!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        numberOfTapsLabel = UILabel()
        numberOfTapsLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(numberOfTapsLabel)
        NSLayoutConstraint.activate([
            numberOfTapsLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            numberOfTapsLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
        
        setUpTapGestureRecognizer()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setUpTapGestureRecognizer() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(incrementNumberOfTaps))
        print("numberOfTapsLabel", numberOfTapsLabel)
        numberOfTapsLabel.addGestureRecognizer(tapGestureRecognizer)
        numberOfTapsLabel.isUserInteractionEnabled = true
    }
    
    @objc func incrementNumberOfTaps() {
        delegate?.incrementNumberOfTaps(index: index)
    }
    
}

Label I Added in Storyboard Appears nil when I try to Dequeue a Cell Using a Data Source Cell Provider
 
 
Q