My code has a UICollectionView inside a UITableViewCell. I have two different set of arrays named TopBrandArray & TopCategoryArray - which is being displayed in two different cells of a Table inside a collectionView.
The delegates and datasource has been connected to the HomeViewController in the main storyboard. Also, the name for the cells has been given.
There is no error while compiling the program but gets an expection while running the code.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier TopCategoryCollectionID - must register a nib or a class for the identifier or connect a prototype cell in a storyboard
When I am trying to run by displaying only one single cell of the tableviewcell, the code works fine. But if am adding more cell the above mentioned error pops up. Here,I am adding the code:
#IBOutlet weak var homePageTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.homePageTable.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "TopCategoryID", for: indexPath) as! TopCategoriesTableViewCell
cell.tablecollection1.reloadData()
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "BrandTableID", for: indexPath) as! BrandsTableViewCell
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 162
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0{
return topCategoryArray.count
}
else{
return topBrandArray.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TopCategoryCollectionID", for: indexPath) as? TopCategoriesCollectionViewCell{
cell.cat_image.image = UIImage(named: topCategoryArray[indexPath.row].image)
print(topCategoryArray[indexPath.row].name)
cell.cat_value.text = topCategoryArray[indexPath.row].name
return cell
}
else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BrandsCollectionID", for: indexPath) as! BrandsCollectionViewCell
cell.layer.borderWidth = 1.5
cell.layer.borderColor = UIColor.gray.cgColor
cell.layer.cornerRadius = 4
cell.brandimage.image = UIImage(named: topBrandArray[indexPath.row].image)
return cell
}
}
}
Since it is asking to register a nib or a class for the identifier, I do not know where inside the code should I write the code because when I wrote it in viewDidload(), an error popped up showing "Ambiguous reference to member collectionView(_:numberOfItemsInSection:)"
The issue is with the registration of the cell with collection view. The collection view is unable to dequeue cell with the "TopCategoryCollectionID" identifier. You need to say to collection view that I'm going to use this cell as collection view cell.
You need to register TopCategoryCollectionID cell to collection view.
let nib = UINib(nibName: "TopCategoryCollectionID", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "TopCategoryCollectionID")
If you have used cell in storyboard then no need to register cell again.
EDIT
As you have collection view inside your tableview cell so datasource and delegate of collection view must be implemented indie the respective cells.
You need to transfer the collection view code into table view cells.
Related
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
}
I am creating a scrollable view filled with monthly calendars. I'm using a collection view to display a calendar inside a table view full of calendars. So each table view cell is a calendar for a specific month, and each collection view cell is a day. I have a separate swift file for the tableview cell from the view controller. Since each tableview cell is going to look different (because different months), the tableview cell needs to know which row it is placed in inside the tableview during its creation in dequeque cell function.
tableView.dequeueReusableCell(withIdentifier: "CalendarTableViewCell", for: indexPath)
I need to get the indexPath in the "for: indexPath" parameter inside the tableview cell file because the collectionview inside the tableview cell gets created when the tableview cell is dequeued. The contents of the collection view depends on which tableview row it's in. So how do I get that parameter?
Sorry for the long explanation, please help if possible. Thank you!
Create an array in UITableViewCell subclass and use that array in collection view data source methods.
class MonthCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
let datesArray = [String]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return datesArray.count
}
}
In tableView cellForRowAt method assign the date values and reload the collectionView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! MonthCell
cell.datesArray = []//dates based on indexPath
cell.collectionView.reloadData()
return cell
}
Or
Assign a reference to the table view cell index path in tableView cellForRowAt method
class MonthCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
var tableIndexPath:IndexPath?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let tableIndexPath {
// return value based on tableIndexPath
} else {
return 0
}
}
}
//cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MonthCell") as! MonthCell
cell.tableIndexPath = indexPath
cell.collectionView.reloadData()
return cell
}
This is a part of my storyboard:
this is my running app:
This is my part of codes:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if indexPath.row == 0 {
return super.tableView(tableView, cellForRowAt: indexPath)
} else {
tableView.register(SubTextFieldCell.self, forCellReuseIdentifier: "SubTextFieldCell")
let cell = tableView.dequeueReusableCell(withIdentifier: "SubTextFieldCell", for: indexPath) as! SubTextFieldCell
// cell.deleteButton.isEnabled = true
// cell.subTextfield.text = "OK"
print("indexPath.row: \(indexPath.row)")
return cell
}
...
I have already connected the button and the textfield in various places and I can guarantee that this part is not wrong, but when I click the Add button in the first row, I only get a cell without any content.
If I use code like this cell.deleteButton..., Xcode will report an error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
Then I tried to use the viewWithTag method to see if show the content, but I still get the same error as before.
This is the first time I have encountered this kind of error. I have no error with similar code and methods in my other programs.
When you configure custom cells inside a storyboard file, you don't need to call register(_:forCellReuseIdentifier:) because the storyboard should have done that for you.
The reason deleteButton is nil is because by re-registering the cell class as you did, you overwrote what the storyboard registered for you. All cells created by dequeueing with that reuse identifier will have no connection to the storyboard and simply be empty.
Assuming all the #IBOutlets and reuse identifiers and things are set up (which you said you did), then simply dequeue the cell with the reuse identifier set up in storyboard.
Dequeue Cell Example:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if indexPath.row == 0 {
return super.tableView(tableView, cellForRowAt: indexPath)
} else {
// Registering again is unnecessary, because the storyboard should have already done that.
// tableView.register(SubTextFieldCell.self, forCellReuseIdentifier: "SubTextFieldCell")
let cell = tableView.dequeueReusableCell(withIdentifier: "SubTextFieldCell") as! SubTextFieldCell
cell.deleteButton.isEnabled = true
cell.subTextfield.text = "OK"
return cell
}
} else {
...
}
}
Note:
Even in cases where you do need to register a class with a table view, you should only have to do this once. (For example, during viewDidLoad)
Even in those times, you should not call it every time you dequeue a cell. You're just making your app work harder.
Connecting views to cells in Storyboard
Set a subclass to the table view
Set a subclass to the first prototype cell
Set a reuse identifier to the prototype cell
Make sure subview (UIButton, etc.) is connected to property with #IBOutlet (subclass code shown below)
Example UITableViewController subclass:
class MyTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyFirstCell", for: indexPath) as! MyFirstTableViewCell
// Configure cell if needed
cell.myButton.setTitle("New Button Text", for: .normal)
cell.myButton.tintColor = .green
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "MySecondCell", for: indexPath) as! MySecondTableViewCell
// Configure cell if needed
cell.myTextField.backgroundColor = .red
return cell
}
}
}
Example UITableViewCell subclass:
class MyFirstTableViewCell: UITableViewCell {
#IBOutlet weak var myButton: UIButton!
}
Result:
So I have collection view with 3 cells, each of which has a tableview in it. Obviously, each collection view cell has to load it's table view with different data for the table view cells.
My first thought was to make the collection view the delegate and datasrouce of the table view, but even so, the datasource will need to know which collection view cell is being loaded.
Also thought of making a separate class for the DS and delegate for both the collection view and the table view, but then again, I'm stuck on how the tableview DS will know which collection view cell it's being loaded from.
Any thoughts?
EDIT:
After I make the assignment, the datasource field is nil. So what am I missing here?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) else {
return UICollectionViewCell.init()
}
cell.backgroundColor = UIColor.clear
cell.theTableView.dataSource = RequestedTableDataSource.init()
cell.theTableView.delegate = self
cell.theTableView.reloadData()
return cell
}
class RequestedTableDataSource: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 16
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "RequestedCell") as? IntervieologistRequestedCell else {
return UITableViewCell.init()
}
cell.setupCellFor(name: "Dan", image: UIImage.init(named: "dan")!)
return cell
}
}
If I do this:
let foo = RequestedTableDataSource.init()
cell.myTableView.dataSource = foo
then the dataSource field is set, numberOfRows gets called, but cellForRowAtIndexPath doesn't.
You can set the tableView.tag to an unique integer and identify it later in the delegate methods using that, but I would recommend keeping a seperate datasource/delegate for each tableview, much cleaner.
Don't make the CollectionView or any other View object the datasource/delegate.
eg set
collectionViewCell.tableView.tag = index;
collectionViewCell.tableView.delegate = commonDelegate;
or
collectionViewCell.tableView.delegate = uniqueDelegate; // Better choice
I Have a UITableView which is controlled by NSFetchedResultsController. I want to add single cell to the first row and make this cell static. In other words, there will be a button which will open another View Controller.
Until now, I was ok with fetched results controller and table. Now I'm a bit confused. How should I do this?
Instead using a header might be ok too, but I don't want this header to be on top all the time. I want this cell to be just like WhatsApp iOS "Create new group" cell on chats panel.
Thank you!
var dataArray = ["A","B","C"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count+1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "CreateNewGroupCell") as! CreateNewGroupCell
return cell
}
else
{
// Get the data from Array
let data = self.dataArray[indexPath.row-1]
// Logic to show other cells
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCell") as! OtherCell
return cell
// ....
}
}
You will need to create tableview with number of rows fetched from NSFetchedResultsController +1. Also in cellForRowIndex method you will need to add a check like indexPath.row == 0 and in there you will make the changes.
Also you will have to add action for that button within that section. You can also set different custom tableview for first row.
It can be similar to following:
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row==0){
let cell = tableView.dequeueReusableCell(withIdentifier: "CellWithButton", for: indexPath) as! CellWithButton
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCells", for: indexPath) as! OtherCells
//here add data for cells from your array
}
return cell
}