0

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

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!

Community
  • 1
  • 1
ConfusionTowers
  • 701
  • 9
  • 26
  • A fundamental question I have, is do you want to use Storyboards or programmatic creation of your UI. You have shown storyboard screenshots, but your code isn't using it. If you use storyboard then all of that registering of classes goes away – Paulw11 Nov 24 '16 at 02:33
  • @Paulw11 Storyboards. Absolutely. And when things run, it's clear that the storyboard is being ignored (i.e. "left" on the left, "right" on the right, yellow space in the middle). But I got into the registering stuff when I got the error "must be initialized with a non-nil layout" after putting the second CollectionView into the original story-boarded version of things. Do I just delete the lines with the "collectionViewLeft.register" and self.view.addSubview(collectionViewLeft), or is there more to it than that? – ConfusionTowers Nov 24 '16 at 02:53
  • You should remove all of that code in `viewDidLoad` except for `super.viewDidLoad` and declare `@IBOutlet weak var collectionViewLeft: UICollectionView!` and `@IBOutlet weak var collectionViewRight: UICollectionView!` then link your collection views to your @IBOutlet in Interface Builder – Paulw11 Nov 24 '16 at 02:56
  • Did that, and back to "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier CollectionViewRightCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'" – ConfusionTowers Nov 24 '16 at 03:04
  • Make sure you have set your reuse identifiers in the prototype cells in the storyboard - Your screenshot shows "cellRight" not "CollectionViewRightCell" – Paulw11 Nov 24 '16 at 03:05
  • Thank you. That finally worked. Left, in red, on the left. Right, in green, on the right. Yellow in the middle. My head is spinning. I need to eat myself into a food coma oblivion tomorrow! Thanks again for your help! – ConfusionTowers Nov 24 '16 at 03:18

2 Answers2

2

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)
Fonix
  • 10,588
  • 2
  • 41
  • 67
  • That worked -- got rid of the error that I was getting! Thank you!!! Now, however, the same step generates an error "Could not cast value of type 'UICollectionViewCell' (0x10e904e98) to 'twoCollectionViews.MyRightCollectionViewCell' (0x10cc8d730)." Any thoughts? – ConfusionTowers Nov 24 '16 at 02:14
  • in your `collectionViewRight.register` and the left one seem to register the same class, is this intentional? cause it seems like its queueing the wrong type of cell for the identifier, see my edited answer – Fonix Nov 24 '16 at 02:16
  • The cell in the left collection view isn't the same as the cell in the right collection view. At this point, it's just a different name for the label, but later there will be more differences between the cells on the left vs on the right. My thought was that the left is thus a different class than the right -- both based (obviously) on UICollectionView, but they're intended to be separate. Did I get that in the collectionView___.register() statement? – ConfusionTowers Nov 24 '16 at 02:22
  • yeah, it seems you are just registering the base UICollectionViewCell to be used in the collectionView, and not your custom cells, hence why the cast is throwing you an error on that line – Fonix Nov 24 '16 at 02:24
  • Yep -- implemented the code you added in the edit to the answer, and things run past the point of the error. Have another error in the next line (I still struggle with unwrapping/optional values), but this should be easier to get through. THANK YOU for your help!!! – ConfusionTowers Nov 24 '16 at 02:26
0

Is there a reason you're using 2 sets of reuse identifiers? Are collectionViewLeftIdentifier and reuseIdentifierLeft meant to be the same thing?

You're registering your cell with collectionViewLeftIdentifier but you are dequeuing with reuseIdentifierLeft. It seems a little bit confusing.

Judging by your screenshot, it looks like your identifier should be cellLeft, which should be under reuseIdentifierLeft.

  • I think that was part of my confusion/misunderstanding, thinking that the two identifiers were supposed to be separate, when that's not the case. – ConfusionTowers Nov 24 '16 at 02:16