Reuse a UICollectionViewCell in multiple UICollectionViews - ios

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
}

Related

How to have custom cell in UICollectionView

I have a UICollectionView that I want to make 3 custom cells appear.
I have read the documentation but I haven't been able to fix this issue.
Is there something I am missing?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
I have tried changing the return 1 to 3 to make 3 custom cells appear but it only makes the 1st custom cell appear 3 times.
I have created a video and linked the video below explaining my situation.
https://www.loom.com/share/9b5802d6cc7b4f9a93c55b4cf7d435bb
Edit I have used #Asad Farooq method and it seems to have worked for me. I added my CollectionView's shown below and I can now make custom cells!
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DailyCollectionViewCell", for: indexPath) as! DailyCollectionViewCell
return cell
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WeeklyCollectionViewCell", for: indexPath) as! WeeklyCollectionViewCell
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MonthlyCollectionViewCell", for: indexPath) as! MonthlyCollectionViewCell
return cell
}
}
As we can see from the documentation of Apple,
You typically don’t create instances of this class yourself. Instead, you register your specific cell subclass (or a nib file containing a configured instance of your class) using a cell registration. When you want a new instance of your cell class, call the dequeueConfiguredReusableCell(using:for:item:) method of the collection view object to retrieve one.
We have to register the cell to the collectionView before using it, for example:
class CustomCollectionViewCell: UICollectionViewCell {
// my custom collection view cell
}
Then we gonna register it to the collection view:
class MyViewController: UIViewController {
...
override func viewDidLoad(){
super.viewDidLoad()
...
self.myCollectionView.dataSource = self
// register the cells, so the collectionView will "know" which cell you are referring to.
self.myCollectionView.register(UINib(nibName: "CustomCollectionViewCell", bundle: nil), forCellReuseIdentifier: "customReuseIdentifier")
// register all type of cell you wanted to show.
}
}
extension MyViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return number of cell you wanted to show, based on your data model
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = routineCollectionView.dequeueReusableCell(withReuseIdentifier: "customReuseIdentifier", for: indexPath) as! CustomCollectionViewCell
// cast the cell as CustomCollectionViewCell to access any property you set inside the custom cell.
// dequeue cell by the reuseIdentifier, "explain" to the collectionView which cell you are talking about.
return cell
}
}
The above code snippet is just a brief example, but I hope that explain the idea.
If you got multiple type of custom cell, you'll have to create classes for them (sub-class of UICollectionViewCell), register them to your collectionView, and dequeue them in collectionView(cellForRowAt:).
There are plenty of tutorial on the internet, here share one of my favourite:
https://www.raywenderlich.com/9334-uicollectionview-tutorial-getting-started
Edit:
If you are using storyboard only to add your custom collectionViewCell, you don't need to register the cell again, the cell already existed in the collectionView (Sorry the above code is just my preference). Just set the class & identifier of the cell, and dequeue the cell using the identifier in collectionView(cellForRowAt:).
We have to register the three different custom cell to the collectionView before using it then inside this function add this code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! cell1
return cell1
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! cell2
return cell2
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell3", for: indexPath) as! cell3
return cell3
}

Why is custom UICollectionViewCell's outlet nil after scroll?

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.

Nested collectionView in a TableView

I've been successfully nesting collections views into tableview for a while now.
What I still don't know how to do, is to do it while respecting the MVC pattern?
Right now I declare my tableview and its cells and in the cell (where the collectionView sits), I attach my collectionView (I got 1 per cell) and do the data mapping. It works, but it's spaghetti code where my View is acting like a controller.
I tried a few times to respect the MVC patterns. I can get my controller to control both my tableview and my collection. Where I struggle is to tell my collection View delegate which data it should pick as all I have as reference is the indexPath (of the collectionView) but not in which tableView that specific collectionView sits.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
The delegate only gives me the indexPath of the cell not which collectionView it is. To use a concrete example - let's assume that my tableview cells represents messages and that each message has a collectionView that controls reactions (like Discord). How do I tell my collectionView delegate which Message it is linked to?
Thanks a lot for the help!
This is very simple. Every view and its subclass has a property tag. You must have an IBOutlet or a simple reference to the CollectionView that sits in the TableViewCell. When you dequeue the TableViewCell just set the tag of your CollectionView equal to the indexPath.row of your tableViewCell like this:
tableViewCell.collectionView.tag = indexPath.row
Then in the UICollectionViewDataSource or UICollectionViewDelegate methods you may find which collectionView it is in other words which tableViewCell does this collectionView sits in. Here's an example:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
let dataObject = dataSourceArray[indexPath.row] as! YourDataModelObject
// Here you may set any property of the collectionView cell
return cell
}
P.S: It is not necessary to respect MVC to that extent. You may manage your CollectionView from the TableViewCell. Have a look at this example.

iOS Use multiple CollectionView reusing the same cell

I'm new to iOS programming but from my experience with Android i know that you can create one design for a item that can be reused in multiple lists, without needing to create a copy.
In iOS i have been trying the same approach with UICollectionView.
In my view i have created three Horizontal UICollectionViews (with different data sets) which in turn use their own cell (Which is identical to the others). I don't know how to make the other two collections use the first ones cell, so i don't need to recreate the same cell over and over.
You can't achieve this using single storyboard. If you want to use Interface Builder (xml) file for layout, you should create MyCell.xib file, then drag UICollectionViewCell on that file and work with that xib.
Then you should connect .xib file with your collection view like this:
let nib = UINib(nibName: "MyCell", bundle: nil)
collectionView?.register(nil, for: "MyCellReuseID")
And you can work with your cell now.
Also you can create MyCell: UICollectionViewCell class without creating .xib file, create views on it programmatically and then call collectionView?.register(MyCell.self, for: "MyCellReuseID")
In your function collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell you can inform your collectionview which cell to use
if you have define your cell you might have something similar to this
class MyCell: UICollectionViewCell{
var Label: UILabel!
var imageView: UIImageView!
}
so into your function collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) you need to inform your collectionview which cell to use
collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:MyCell = collectionView.dequeueReusableCellWithReuseIdentifier("myCell", forIndexPath: indexPath) as! MyCell
[...]
}
and dont forget to register your cell first
override func viewDidLoad() {
super.viewDidLoad()
//do your stuff
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.registerClass(MyCell.self, forCellWithReuseIdentifier: "myCell")
}
and Voila !
Create new file collection cell with XIB
In your "item at row for index" delegate method in view controller, initialize cell and return it.
Now any collection view within you view controller have the same cell
Create a Swift class(CollectionViewCell) by having UICollectionViewCell as a base class. Select create nib option while doing that.
Then create a view controller and add collection view to it in your storyboard and hook up this custom cell class in Identity Inspector. Basically, your structure needs to look like below

iOS custom keyboard app extension UICollectionView not scrolling smoothy

I'm building a custom keyboard, I'm trying to recreate the iOS emoji keyboard. So I created a UICollectionView and each cell contains an emoji (UILabel).
But it's not scrolling smoothy. I don't know if it comes from the UICollectionView of something else.
Emojis are in a plist file, I open it in viewDidLoad
I'm using a xib cell file for my UICollectionViewCell (since there is no other way with a custom keyboard)
I made a test by removing the UILabel from the UICollectionViewCell and it's still not scrolling smoothy
Any clue?
EDIT
var emojis=[[String]]()
override func awakeFromNib(){
dataSource=self
delegate=self
let nib=UINib(nibName: "EmojiKeyCell", bundle: nil)
register(nib, forCellWithReuseIdentifier: cellId)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell=collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! EmojiKeyCell
let emoji=emojis[indexPath.section][indexPath.item]
cell.emojiKey.text=emoji
return cell
}

Resources