Why is custom UICollectionViewCell's outlet nil after scroll? - ios

I wrote a custom component that uses UICollectionView and this UICollectionView has also custom UICollectionViewCell class with xib file.
In my UIViewController class viewDidLoad method, I send a web service request and after the response comes I register my custom UICollectionViewCell with this code;
let bundle = Bundle(for: MyCustomCellClass.self)
let nib = UINib(nibName: "MyCustomCellClass", bundle: bundle)
collectionView.register(nib, forCellWithReuseIdentifier: MyCustomCellClass.reuseIdentifier)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.reloadData()
Then in my UICollectionView cellForItemAt I dequeue cell with this way;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCustomCellClass.reuseIdentifier, for: indexPath) as? MyCustomCellClass else {
fatalError("Cell should be registered")
}
let carObject = myList![indexPath.row])!
cell.carImageView.image = carObject.image
return cell
}
The problem exists after I scroll, the car image has gone. That is because my MyCustomCellClass image view outlet becomes suddenly nil.
I can't find the solution, any help would be greatly appreciated.

Related

How to connect custom collectionviewCell to reuseidentifier with XIB - Swift

I am making a custom collectionview cell Using XIB.
The collectionview is placed inside an viewController as an extension.
This is the code i am using to call the Xib View but i get an error telling me i need to use reuseidentifier. But i have no clue how to use that while using XIB.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = Bundle.main.loadNibNamed("CustomCell", owner: self, options: nil)?.first as! CustomCell
return cell
}
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:'
*** First throw call stack:
First you need to create a reuseIdentifier for your cell. Lets create it based on your collectionViewCell class name. Declare reuseId, in your ViewController file:
let reuseId = String(describing: CustomCell.self)
You need to register your cell to your collectionView in viewDidLoad method.
collectionView.register(CustomCell.self, forCellReuseIdentifier: reuseId)
Then in your cellForItemAt method:
guard let cell = collectionView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
//return cell, or update cell elements first.
You can register the CustomCell like,
let customCellNib = UINib(nibName: "CustomCell", bundle: .main)
collectionView.register(customCellNib, forCellWithReuseIdentifier: "CustomCell")
And use the same registered cell in cellForItemAt like,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"CustomCell", for: indexPath) as? CustomCell else {
return UICollectionViewCell()
}
return cell
}
For Swift 4.0 and 4.2
In your viewDidLoad:
custom collectionViewCell
mainCollectionView.register(UINib(nibName: "your_custom_cell_name", bundle: nil), forCellWithReuseIdentifier: "your_custom_cell_identifier")
In cellForItemAt indexPath:
let cell : <your_custom_cell_name> = mainCollectionView.dequeueReusableCell(withReuseIdentifier: "your_custom_cell_identifier", for: indexPath) as! <your_custom_cell_name>
And don't forget to set identifier for your custom cell in xib section.

Reuse a UICollectionViewCell in multiple UICollectionViews

I want to do something similar to this where A, B and C are all different UICollectionViewController. My current approach is manually duplicating the cell in storyboard to all different UICollectionViews but have they all have same custom subclass. However, it's not DRY for UI development and I have to manually change replicate the changes in each UICollectionViewCell copy.
I know XIBs can be used to achieve this, but I'm stuck at how to link it with all UICollectionViewController through storyboard or minimal Swift code.
#Anushik use below steps to create custom cell using xib and reuse
1.Create UICollectionViewCell subclass along with xib
2.Add reuse identifier to the cell
3.Register the nib using register(nib:forCellWithReuseIdentifier) method in viewDidLoad method in UIViewController class
let nib = UINib(nibName: "CustomCollectionViewCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "CustomCell")
4.Use the custom cell in cellForItemAt method of UICollectionViewDatasource
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCollectionViewCell
let item = items[indexPath.row]
cell.configureCell(item: item)
return cell
}

UICollectionView with custom cell, fatal error: unexpectedly found nil while unwrapping an Optional value

I used UICollectionView to display my data, and I used a custom cell, in cellForItemAt fun App crashed when I want to assign value to label.
let reuseIdentifier = "brandCell"
ViewDidLoad------>
self.brandCollectionView!.register(BrandCercaCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
brandCollectionView.dataSource = self
brandCollectionView.delegate = self
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! BrandCercaCollectionViewCell
//App crashed here --->
cell.brandNameLabel.backgroundColor = UIColor.blue
return cell
}
I attached photos for storyBoard.
Thanks.
Make sure you set the identifier of your cell "brandCell" in the storyboard as the identifier for the collection view cell.

How to clear a UICollectionViewCell on reload?

I have an issue where the data presented in a UICollectionView overwrites the label and the cell view is not getting cleared.
This image shows the issue,
IE:
My UICollectionViewCell which is constructed like so;
// in viewDidLoad
self.playerHUDCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier:reuseIdentifer)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifer, for: indexPath) as UICollectionViewCell
let arr = UINib(nibName: "EYPlayerHUDView", bundle: nil).instantiate(withOwner: nil, options: nil)
let view = arr[0] as! EYPlayerHUDView
cell.contentView.addSubview(view)
if let allPlayers = self.allPlayers
{
let player:EYPlayer = allPlayers[indexPath.row]
view.updatePlayerHUD(player: player)
}
cell.layoutIfNeeded()
return cell
}
I use a view to display in the cell.
I tried removing all the cell's subchildren in the cellForItemAt but it appears to remove all the subviews.
I would like to know how do I clear the UICollectionViewCell so labels and other info on the UICollectionViewCell is not dirty like the example above.
Many thanks
Use prepareForReuse method in your custom cell class, something like this:
override func prepareForReuse() {
super.prepareForReuse()
//hide or reset anything you want hereafter, for example
label.isHidden = true
}
in your cellForItemAtIndexPath, instantiate your custom cell:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellIdentifier", for: indexPath) as! CustomViewCell
Then, always in cellForItemAtIndexPath, setup your items visibility/values
//cell = UICollectionViewCell
for subview in cell.contentView.subviews {
// you can place "if" condition to remove image view, labels, etc.
//it will remove subviews of cell's content view
subview.removeFromSuperview()
}
UICollectionViewCells are reused to avoid instantiations, to optimize the performance. If you are scrolling and a cell becomes invisible, the same object is used again (dequeueReusableCell) and a new content is set in cellForItemAt...
As mentioned in the previous answers, before reusing the cell, prepareForReuse() is called on the cell. So you can overrride prepareForReuse() and do whatever preparation you need to do.
You are however creating and adding a new EYPlayerHUDView to the cell on every reuse, so your cell becomes full of stacked EYPlayerHUDViews.
To avoid this, subclass UICollectionViewCell and make the EYPlayerHUDView a property of your custom cell (I recommend to use a XIB):
class MyCell: UICollectionViewCell {
#IBOutlet var player:EYPlayerHUDView!
override func prepareForReuse() {
super.prepareForReuse()
// stop your player here
// set your label text = ""
}
}
After doing so, you can update the EYPlayerHUDView in cellForItemAt without instantiating it and without adding it as new view:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifer, for: indexPath) as? MyCell else {
return nil
}
if let allPlayers = self.allPlayers {
let player:EYPlayer = allPlayers[indexPath.row]
cell.player.updatePlayerHUD(player: player)
}
return cell
}
(Code untested)
Make custom UICollectionView class and implement prepareForReuse to clear the content if needed.

UICollectionView inside TableView not populating cells?

I have a tableView with rows that need to be horizontally flowing collectionViews.
My tableView cells have a single collectionView within them, and then I instantiate them like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyTableViewCell = tableView.dequeueReusableCellWithIdentifier("MyTableViewCell", forIndexPath: indexPath) as! MyTableViewCell
let nibName = UINib(nibName: "CollectionCell", bundle: nil)
cell.collectionView.registerNib(nibName, forCellWithReuseIdentifier: "CollectionCell")
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
return cell
}
I'm trying to use an auto-height table view row, and I think this is what could be causing issues. How do I use UITableViewAutomaticDimension with internal collection views?
This is in objective-c, but works for me:
i. In your custom cell's .m file register the UICollectionViewCell nib, and specify you layout details (size, spacing etc.).
ii. in the VC that holds the tableView its .m do the following in cellForRowAtIndexPath
MyTableViewCell *cell = (MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"MyTableViewCellId" forIndexPath:indexPath];
cell.collectionView.delegate = self;
cell.collectionView.dataSource = self;
cell.collectionView.tag = indexPath.row;
[cell.collectionView reloadData];
iii. You can use the tag of the UICollectionView in the delegate methods to populate the right information
Hop this helps.
Try adding - cell.setTranslatesAutoresizingMaskIntoConstraints(false) - see below
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyTableViewCell = tableView.dequeueReusableCellWithIdentifier("MyTableViewCell", forIndexPath: indexPath) as! MyTableViewCell
let nibName = UINib(nibName: "CollectionCell", bundle: nil)
cell.collectionView.registerNib(nibName, forCellWithReuseIdentifier: "CollectionCell")
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
cell.setTranslatesAutoresizingMaskIntoConstraints(false)
return cell
}
See https://www.captechconsulting.com/blogs/ios-8-tutorial-series-auto-sizing-table-cells for some more information.

Resources