Linkon Sid
Linkon Sid

Reputation: 531

UICollectionView error: cells must be retrieved by calling -: -dequeueReusableCellWithReuseIdentifier:forIndexPath:

In my project I have multiple types of UITableview cell each containing UICollectionview.

Initially I loaded 2 tableViewcell in 2 sections of UITableview. At first it loads fine without any problem. If I scroll the first section it scrolls nice but when I try to scroll the second section it gives me the following error:

the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:

I have the following code to set up the process:

    class Home: UITableViewDelegate, UITableViewDataSource, UICollectionViewDelegate, UICollectionViewDataSource{

     override func viewDidLoad() {
        self.tableView.register(SliderViewCell.nib, forCellReuseIdentifier: SliderViewCell.identifier)
        self.tableView.register(GridViewCell.nib, forCellReuseIdentifier: GridViewCell.identifier)
        self.tableView.estimatedRowHeight = 80
        self.tableView.rowHeight = UITableViewAutomaticDimension

}
func numberOfSections(in tableView: UITableView) -> Int {
    return 2
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    let viewObj = self.viewObject[indexPath.section]
    if viewObj.itemType! == HomeViewItemType.Slider.rawValue{
    if let sliderCell = cell as? SliderViewCell {
        sliderCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
    }
    }
    else if viewObj.itemType! == HomeViewItemType.Grid.rawValue{
    if let gridCell = cell as? GridViewCell {
        gridCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
    }
    }


}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if self.viewObject.count>0{
    let viewObj = self.viewObject[indexPath.section]
    if viewObj.itemType! == HomeViewItemType.Slider.rawValue{
        let cell:SliderViewCell = tableView.dequeueReusableCell(withIdentifier: SliderViewCell.identifier, for: indexPath) as! SliderViewCell
        cell.contentView.tag = indexPath.section
    return cell
}
else if viewObj.itemType! == HomeViewItemType.Grid.rawValue{
        let cell:GridViewCell = tableView.dequeueReusableCell(withIdentifier: GridViewCell.identifier, for: indexPath) as! GridViewCell
        cell.contentView.tag = indexPath.section
        return cell
}
    }
    return UITableViewCell()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("selected")
}

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

        return self.viewObject[section].viewData.count

}

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

    if viewObj.itemType! == HomeViewItemType.Slider.rawValue{
        if let cell:SliderCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: SliderCollectionViewCell.identifier, for: indexPath) as? SliderCollectionViewCell{

            if viewObj.viewData.count>0{
                if let imageURL = URL(string: viewData.imageLink!){
                    cell.icon.sd_setImage(with: imageURL, completed: nil)
                }
            }
            return cell
        }
    }
    else if viewObj.itemType! == HomeViewItemType.Grid.rawValue{
if let cell:GridCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: GridCollectionViewCell.identifier, for: indexPath) as? GridCollectionViewCell{

            if viewObj.viewData.count>0{
                if let imageURL = URL(string: viewData.icon!){
                    cell.icon.sd_setImage(with: imageURL, completed: nil)
                }

            }

            return cell
}
    }

    return UICollectionViewCell()
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    print("tapped")
}

    }

class SliderViewCell: UITableViewCell {

@IBOutlet weak var collectionView: UICollectionView!
static var nib:UINib {
    return UINib(nibName: identifier, bundle: nil)
}
static var identifier:String {
    return String(describing: SliderViewCell.self)
}
var collectionViewCellWidth : CGFloat = 15.0
var collectionViewCellHeight : CGFloat = 150.0
var cellSpaceWidth : CGFloat = 8.0

override func awakeFromNib() {
    super.awakeFromNib()
    self.configureCollectionViewCell(nibFile: SliderCollectionViewCell.nib, identifier: SliderCollectionViewCell.identifier, width: collectionView.frame.size.width, height: 80, topInset: 0, leftInset: 0, bottomInset: 0, rightInset: 0, cellSpace: cellSpaceWidth, interimSpace: 0.0, scrollInDirection: .horizontal)
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}
}
class GridViewCell: UITableViewCell {

@IBOutlet weak var collectionView: UICollectionView!
static var nib:UINib {
    return UINib(nibName: identifier, bundle: nil)
}
static var identifier:String {
    return String(describing: GridViewCell.self)
}
var collectionViewCellWidth : CGFloat = 15.0
var collectionViewCellHeight : CGFloat = 150.0
var cellSpaceWidth : CGFloat = 8.0

override func awakeFromNib() {
    super.awakeFromNib()
    self.configureCollectionViewCell(nibFile: GridCollectionViewCell.nib, identifier: GridCollectionViewCell.identifier, width: collectionView.frame.size.width, height: 80, topInset: 0, leftInset: 0, bottomInset: 0, rightInset: 0, cellSpace: cellSpaceWidth, interimSpace: 0.0, scrollInDirection: .horizontal)
}

func configureCollectionViewCell(nibFile:UINib, identifier:String,width:CGFloat,height:CGFloat, topInset:CGFloat,leftInset:CGFloat,bottomInset:CGFloat,rightInset:CGFloat, cellSpace:CGFloat, interimSpace:CGFloat,scrollInDirection:UICollectionViewScrollDirection){

    let nib = nibFile
    collectionView.register(nib, forCellWithReuseIdentifier: identifier)
    let cellWidth : CGFloat = width//collectionView.frame.size.width
    let cellheight : CGFloat = height//collectionViewCellHeight
    let cellSize = CGSize(width: cellWidth , height:cellheight)
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = scrollInDirection
    layout.itemSize = cellSize
    layout.sectionInset = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
    layout.minimumLineSpacing = cellSpace
    layout.minimumInteritemSpacing = interimSpace
    collectionView.setCollectionViewLayout(layout, animated: true)
    collectionView.reloadData()

}
override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}
    }

    extension GridViewCell{

func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forRow row:Int){

    collectionView.delegate = dataSourceDelegate
    collectionView.dataSource = dataSourceDelegate
    collectionView.reloadData()

}
}

I think the GridViewCell which loads in the 2nd section of UITableview causing the problem. But I've already added dequeueReusableCellWithReuseIdentifier in the collectionView:cellForItemAtIndexPath: method. So I'm clueless what's causing the error.

Any help would be greatly appreciated.

Upvotes: 19

Views: 19388

Answers (8)

Abhishek B
Abhishek B

Reputation: 187

I have also faced this issue. make sure your cellForItemAt method should not return UICollectionViewCell() it should return the cell which is dequeued.

Check if you have added conditions which are failing and your default return is getting called and returning UICollectionViewCell() then this crash will come.

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

    return UICollectionViewCell()
}

Upvotes: 0

keshav
keshav

Reputation: 178

i resolve this problem by i missed to return cell in one collection view. i am using multiple collection view and i missed to return cell in "

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

see somewhere you missed to return cell

Upvotes: 2

Tesleem
Tesleem

Reputation: 95

Had this issue today. Truly the phrase "return cell" where cell is a UICollectionViewCell causes this issue.

My issue was because I was using guard let to unwrap an optional, and due to the fact that the function returns something, which in this case is a UICollectionViewCell, it expects that I return one. So I returned a UICollectionViewCell to shut out the error. Definitely, it came back to hunt me.

guard let model  = titles[indexPath.row].posterPath else {
   print("something went wrong")
   return TitleCollectionViewCell()}

So i was able to resolve by using nil coalescing which would not require that I return anything like in guard let method.

let model = titles[indexPath.row].posterPath ?? ""
return cell

Upvotes: 0

Akram Husseini
Akram Husseini

Reputation: 29

this happened to me too, with the following code:

func collectionView(_ collectionView: UICollectionView, cellForItemAt 
indexPath: IndexPath) -> UICollectionViewCell {
    
        guard let cell = 
collectionView.dequeueReusableCell(withReuseIdentifier: 
"selectedUserCell", for: indexPath) as? selectedUserCell  else { return 
UICollectionViewCell() }
    
        return cell
    
    
    }

The problem was that I forgot to assign the class selectedUserCell to the cell in storyboard/nib.. so once that was fixed, the correct cell was being returned.

Upvotes: 2

Moaz Khan
Moaz Khan

Reputation: 1352

Here are two ways that you can use to handle your cells instead of if else then UICollectionViewCell()

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)

        if let gridCell = cell as? GridCollectionViewCell {
            // TODO: configure cell
        }

return cell

Another way is to

    guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
 as? GridCollectionViewCell else {

                //Handle error first so you can return a perfect cell if there is no error
  }

Remove UICollectionViewCell() it's causing the issue because there is no reuse identifier for that cell.

Upvotes: 4

AechoLiu
AechoLiu

Reputation: 18368

I doubt the error come from this line return UICollectionViewCell(), could you use assert to check error condition ?

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

    // ....

    // I doubt error come from `return UICollectionViewCell()`
    // Could you insert `assert` to check error condition ?
    // assert(false,'Error condition for collectionView cell')
    return UICollectionViewCell()
}

Upvotes: 0

Mahendra
Mahendra

Reputation: 8904

Select collection view cell and add reuse identifier

enter image description here

Upvotes: 1

John Snow
John Snow

Reputation: 462

The problem may be caused because you are returning UICollectionViewCell() in

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

if the two conditions are not met.

If at any point the conditions are not met, you return a cell which is not retrieved by calling dequeueReusableCellWithReuseIdentifier:forIndexPath: as the exception says.

Try setting a breakpoint to confirm if this is what is actually happening.

Upvotes: 24

Related Questions