ConfusionTowers ConfusionTowers - 12 days ago 4
iOS Question

Error implementing a 2nd UICollectionView: could not dequeue a view of kind: UICollectionElementKindCell with identifier xxxx - must register

This question is a follow-up to this previous question.

I've implemented one UICollectionView, but can't get the project to run when I implement a second one. Where things are dying is at the line in

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


where I first start to set up the prototype cell. I have a left and a right collection view, with the cell in each named cellLeft and cellRight respectively, and those names stored in constants reuseIdentifierLeft and reuseIdentifierRight. (No extra space in the constants -- common issue in other answers, checked for that.) The statement where things crash is

let rightCell = collectionView.dequeueReusableCell(withReuseIdentifier:
reuseIdentifierRight, for: indexPath as IndexPath) as!
MyRightCollectionViewCell


The error message is


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier cellRight - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'


The obvious point of failure is not having correctly registered the cell and/or the label, although I've been through every possibility for doing that I can think of, and nothing seems to help. (The collection view on the left side was working fine when it was the only CollectionView in the project, so I thought I knew how to hook things up correctly. But when things initialize, the right side is getting initialized first, so I can't tell if the left side would fail like this one is, or not.)

I've spent literally hours checking that things are hooked up, and as best I can tell, they are. Take this out, put it back together, make sure it's all there, run it again, same crash.

Screenshots of my work:
Screenshot of the Cell + Label, Attributes & Connections inspectors

(Sorry -- I'd post individual screenshots, but < 10 reputation.)

Code:

ViewController.swift

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

var collectionViewLeft: UICollectionView!
var collectionViewRight: UICollectionView!

let collectionViewLeftIdentifier = "CollectionViewLeftCell"
let collectionViewRightIdentifier = "CollectionViewRightCell"

override func viewDidLoad() {
super.viewDidLoad()

let layoutLeft = UICollectionViewFlowLayout()
layoutLeft.itemSize = CGSize(width: 100, height: 100)

let layoutRight = UICollectionViewFlowLayout()
layoutRight.itemSize = CGSize(width: 100, height: 100)

collectionViewLeft = UICollectionView(frame: self.view.frame, collectionViewLayout: layoutLeft)
collectionViewRight = UICollectionView(frame: self.view.frame, collectionViewLayout: layoutRight)

collectionViewLeft.delegate = self
collectionViewRight.delegate = self

collectionViewLeft.dataSource = self
collectionViewRight.dataSource = self

collectionViewLeft.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewLeftIdentifier)
collectionViewRight.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewRightIdentifier)

self.view.addSubview(collectionViewLeft)
self.view.addSubview(collectionViewRight)

print("Both subviews added ok.") // this DOES print.
}

let reuseIdentifierLeft = "cellLeft"
let reuseIdentifierRight = "cellRight"

var itemsRight = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48"]
var itemsLeft = ["10", "20", "30", "40", "50", "60"]


// MARK: - UICollectionViewDataSource protocol

// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

if collectionView == self.collectionViewLeft {
return self.itemsLeft.count
} else if collectionView == self.collectionViewRight {
return self.itemsRight.count
} else {
print("This is very bad")
assert(false, "Passed collectionView is neither collectionViewLeft nor collectionViewRight -- ruh roh!")
return 0
}
}

// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

if collectionView == self.collectionViewLeft {

print("Attempting Left...") // not observed -- 'right' is evidently getting called first.
// get a reference to our storyboard cell
let leftCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierLeft, for: indexPath as IndexPath) as! MyCollectionViewCell
print("hit let leftCell = collectionView.dequeue...")
leftCell.myLeftLabel.text = self.itemsLeft[indexPath.item]
leftCell.backgroundColor = UIColor.red // make cell more visible in our example project
return leftCell
} else if collectionView == self.collectionViewRight {

print("Attempting Right... reuseIdentifierRight='" + reuseIdentifierRight + "'") // this prints: "Attempting Right... reuseIdentifierRight='cellRight'" which looks okay.

let rightCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierRight, for: indexPath as IndexPath) as! MyRightCollectionViewCell


// ^
// /|\
// / | \
// |
// |
//
// Dies here.
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier cellRight - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

// attempted, no help:
// let rightCell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifierRight, for: indexPath as IndexPath) as! MyRightCollectionViewCell
// i.e. adding "self" into "self.reuseIdentifierRight"


print("Successfully hit 'let rightCell = collectionView.dequeue...'") // does NOT print.

// Use the outlet in our custom class to get a reference to the UILabel in the cell
rightCell.myRightLabel.text = self.itemsRight[indexPath.item]
rightCell.backgroundColor = UIColor.green
return rightCell
} else {
print("This is very bad")
assert(false, "Passed collectionView is neither collectionViewLeft nor collectionViewRight -- ruh roh!")
// won't actually execute the following, but to keep the compiler happy...
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierLeft, for: indexPath as IndexPath) as! MyCollectionViewCell
return cell
}
}

// MARK: - UICollectionViewDelegate protocol

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

// handle tap events

if collectionView == self.collectionViewLeft {
print("You tapped cell #\(indexPath.item) on the LEFT!")
} else if collectionView == self.collectionViewRight {
print("You tapped cell #\(indexPath.item) on the RIGHT!")
} else {
print("This is very bad")
assert(false, "Passed collectionView is neither collectionViewLeft nor collectionViewRight -- ruh roh!")
}
}
}


MyCollectionViewCell.swift

import UIKit

class MyCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var myLeftLabel: UILabel!
}


MyRightCollectionViewCell.swift

import UIKit

class MyRightCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var myRightLabel: UILabel!
}


I am absolutely at wit's end here, having looked through various Stack Exchange articles & having tried everything in them I can find, but nothing seems to change what's crashing. Am I missing something simple & obvious about how the cell and/or label need to be hooked up to each other? Thanks for any help that can be offered -- it will be most greatly appreciated!

Answer

Get rid of

let reuseIdentifierLeft = "cellLeft"
let reuseIdentifierRight = "cellRight"

and change your dequeue to

let rightCell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewRightIdentifier, for: indexPath as IndexPath) as! MyRightCollectionViewCell

For both the left and right collection view

The identifier you register in collectionViewRight.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewRightIdentifier) is the reuseIdentifier, and shouldnt be a separate identifier

also change the class that is being registered for each cell to the appropriate ones, eg

collectionViewLeft.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: collectionViewLeftIdentifier)
collectionViewRight.register(MyRightCollectionViewCell.self, forCellWithReuseIdentifier: collectionViewRightIdentifier)