I know it is possible that we can load multiple uitableview cells under one xib in Objective C but is it possible also in swift?
I tried to use same logic I used in Objective C
var cellAuditDetails:AuditDetailsTableViewCell! = tableView.dequeueReusableCellWithIdentifier("customTrialDetailsCell", forIndexPath: indexPath) as! AuditDetailsTableViewCell
if indexPath.row == 0{
if(cellAuditDetails == nil)
{
cellAuditDetails = NSBundle.mainBundle().loadNibNamed("AuditDetailsTableViewCell", owner: self, options: nil)[0] as! AuditDetailsTableViewCell;
}
}
else{
cellAuditDetails = NSBundle.mainBundle().loadNibNamed("AuditDetailsTableViewCell", owner: self, options: nil)[1] as! AuditDetailsTableViewCell;
}
But got the following error ***Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (customTrialDetailsCell) - nib must contain exactly one top level object which must be a UITableViewCell instance'
Now If I use only one cell then its fine. But how do I load multiple cells under same xib? Because its irritating to take another xib for each new cell.
Somehow I managed to solve this problem in Swift 3. Thanks to #waris-shams. Here is the solution:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "customCell")
if indexPath.row == 0{
if(cell == nil)
{
let cellAuditDetails:AuditDetailsTableViewCell = Bundle.main.loadNibNamed("AuditDetailsTableViewCell", owner: self, options: nil)? [0] as! AuditDetailsTableViewCell
cell = cellAuditDetails
}
}
else{
if(cell == nil)
{
let cellAuditDetails:AuditDetailsTableViewCell = Bundle.main.loadNibNamed("AuditDetailsTableViewCell", owner: self, options: nil)? [1] as! AuditDetailsTableViewCell
cell = cellAuditDetails
}
}
}
It is possible if you distinguishing the views in your XIB (tags, custom classes, etc), then you don't even need to register the nib – all you have to do is this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cellIndex = 12345
var cellIdentifier = "MyAwesomeCell"
// dequeue cell or, if it doesn't exist yet, create a new one from the nib
var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
if !cell {
var topLevelObjects = Bundle.main.loadNibNamed("MyNibWithLotsOfCustomCells", owner: self, options: nil)
var index = (topLevelObjects as NSArray).indexOfObject(passingTest: {(_ obj: Any, _ idx: Int, _ stop: Bool) -> BOOL in
// if you distinguish the views by class
return type(of: obj) === NSClassFromString(cellIdentifier)
// if you distinguish the views by tag
return (obj as! UIView).tag == cellIndex
})
cell = topLevelObjects[index]
}
}
and one more thing to remember that use
dequeueReusableCell(withIdentifier:cellIdentifier)
instead of
dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
Related
I'm using two TableViews in a ViewController but I get this error when it gets to the second TableViewCell, cartProductCell. They both have custom classes, and outlets, as it was the problem for many in other posts. Is the first time I'm doing this and I can't find a solution for this. May it be just because I'm using custom classes for the cells? In the tutorials I found about two TableViews weren't used custom classes.
In the Storyboard editor everything is connected well and identifiers are both correct.
Here's the function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
// if tableView == self.worksTableView && CartViewController.bookedWoksArray.count > 0 {
if tableView == self.worksTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartWorkCell", for: indexPath as IndexPath) as! CartWorkTableViewCell
cell.workImageView.image = CartViewController.bookedWoksArray[indexPath.row].0
cell.workNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
cell.workPriceLabel.text = CartViewController.bookedWoksArray[indexPath.row].2
} // else {return}
// else if tableView == self.worksTableView && CartViewController.bookedProductsArray.count > 0 {
if tableView == self.worksTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartProductCell", for: indexPath as IndexPath) as! CartProductTableViewCell
cell.cartProductImageView.image = CartViewController.bookedProductsArray[indexPath.row].0
cell.cartProductNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
cell.cartProductPriceLabel.text = CartViewController.bookedProductsArray[indexPath.row].2
} //else {return}
return cell!
}
As usual many thanks
After a few tries and after fixing a silly mistake I finally made it work by assigning the value of custom cells to cell and the function's code is now:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
if tableView == self.worksTableView {
let workCell = tableView.dequeueReusableCell(withIdentifier: "cartWorkCell", for: indexPath) as! CartWorkTableViewCell
workCell.workImageView.image = CartViewController.bookedWoksArray[indexPath.row].0
workCell.workNameLabel.text = CartViewController.bookedWoksArray[indexPath.row].1
workCell.workPriceLabel.text = CartViewController.bookedWoksArray[indexPath.row].2
cell = workCell
}
if tableView == self.productsTableView{
let productCell = tableView.dequeueReusableCell(withIdentifier: "cartProductCell", for: indexPath) as! CartProductTableViewCell
productCell.cartProductImageView.image = CartViewController.bookedProductsArray[indexPath.row].0
productCell.cartProductNameLabel.text = CartViewController.bookedProductsArray[indexPath.row].1
productCell.cartProductPriceLabel.text = CartViewController.bookedProductsArray[indexPath.row].2
cell = productCell
}
return cell!
}
I have a table view that has several sections, two sections are almost identical but their header names are different. so I have one registered nib and I'm using it in two rows:
self.tableView.register(UINib(nibName: "ImprestDetailInfoHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "PaymentsReceiptsDetailsHeader")
and In my cellforrowat for table view I have this:
case 2:
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaymentsReceiptsDetailsHeader", for: indexPath) as! ImprestDetailInfoHeaderTableViewCell
cell.selectionStyle = .none
cell.headerType = ImprestDetailInfoType.payment
cell.checkedAndTotalNumber = self.imprestViewModel.getStringCheckedAndTotalNumberOfPayments()
self.paymentHeaderDelegate = cell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddImprestPaymentsReceiptsDetailTableView", for: indexPath) as! ImprestReceiptPaymentDetailTableViewCell
cell.imprestItems = self.imprestViewModel.getPaymentsImprestItems()
cell.delegate = self.imprestViewModel
return cell
}
case 3:
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaymentsReceiptsDetailsHeader", for: indexPath) as! ImprestDetailInfoHeaderTableViewCell
cell.selectionStyle = .none
cell.headerType = ImprestDetailInfoType.receipt
cell.checkedAndTotalNumber = self.imprestViewModel.getStringCheckedAndTotalNumberOfReceipts()
self.receiptHeaderDelegate = cell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddImprestPaymentsReceiptsDetailTableView", for: indexPath) as! ImprestReceiptPaymentDetailTableViewCell
cell.imprestItems = self.imprestViewModel.getReceiptsImprestItems()
cell.delegate = self.imprestViewModel
return cell
}
as you see first row of 2nd and 3rd sections have identical definitions. when I click on either of these rows, I need them to show their detailed information and I change an image icons on headers, but this seems to be have a conflicting behaviour, so when I click on first row, this changes icons of both and similar strange things. Is there any chance that this problem be cus of that two cells use same nib identifier?
cus I checked my delegates and I found no problem there, the only thing I can think is some bug in swift memory management for these situations.
UPD: didSelectRowAtIndexPath:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 1 {
self.delegate?.summartySectionDidTapped()
} else if indexPath.section == 2 && indexPath.row == 0 {
self.paymentsDetailIsOn.toggle()
self.paymentHeaderDelegate?.detailHeaderSelected()
} else if indexPath.section == 3 && indexPath.row == 0 {
self.receiptsDetailIsOn.toggle()
self.receiptHeaderDelegate?.detailHeaderSelected()
}
}
I changed
self.tableView.register(UINib(nibName: "ImprestDetailInfoHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "PaymentsReceiptsDetailsHeader")
to
self.tableView.register(UINib(nibName: "ImprestDetailInfoHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "PaymentsDetailsHeader")
self.tableView.register(UINib(nibName: "ImprestDetailInfoHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "ReceiptsDetailsHeader")
and it seems that the problem fixed!!! (different ids for different cells)
If you want to insert an header in sections you can use the function func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
Is more simple to implement the tableView as you want
I have currently have two UITableViews populated with contacts for the app. I have one for simply viewing them and editing/deleting and one for searching/picking contacts from a list. However, I'm getting a returned nil value when trying to use the same custom class cell for both UITableViews.
These are my two cellForRowAtIndexPath functions.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("SecondCell") as! ContactCell
let item = contacts[indexPath.row]
cell.meetupLabel?.text = item.fullName
return cell
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("FirstCell") as! ContactCell
let item = contacts[indexPath.row]
cell.label?.text = item.fullName
return cell
}
If the table did not have a cell named FirstCell or SecondCell, the dequeueReusableCellWithIdentifier(_:) method will return nil, and you will need to construct the cell yourself.
// no don't do this.
let cell: ContactCell
if let c = tableView.dequeueReusableCell(withIdentifier: "FirstCell") as? ContactCell {
cell = c
} else {
cell = ContactCell(style: .default, reuseIdentifier: "FirstCell")
}
You should use dequeueReusableCell(withIdentifier:for:), which was introduced in iOS 6, if you would like UIKit to construct the cell for you:
// swift 3
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell",
for: indexPath) as! ContactCell
// swift 2
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell",
forIndexPath: indexPath) as! ContactCell
...
Also, check if you have given the correct reuse-identifiers to the cells correctly in the interface builder.
As you said you are getting nil, my quick guess is that you haven't registered the cell at some point, runs earlier than this cell event. Look at this thread on how to register cell.
I want to create a table view that loads its data from two different data sources and chooses a specific nib depending on which one it is. How can I create a feed that mixes elements from two arrays of objects?
I was thinking about creating an array of objects with the objects being sorted by date. Then, I would check what the type of each object is at my cellforrowatindexpath method and use the nib that corresponds.
Is this the most efficient way of doing this?
Thank you
Creating a single array of objects and dealing with them correctly in cellForRowAtIndexPath is a good approach.
You should register each of your nibs with the table view, probably in viewDidLoad using registerNibForCellReuseIdentifier. Make sure you use a different reuseIdentifier for each, then dequeue the correct type in cellForRowAtIndexPath, once you've determined which type of cell it should be.
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerNib(UINib(nibName: "fooCell", bundle: .mainBundle()), forCellReuseIdentifier: "fooCellIdentifier");
tableView.registerNib(UINib(nibName: "barCell", bundle: .mainBundle()), forCellReuseIdentifier: "barCellIdentifier");
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if (indexPathIsFooCell(indexPath)) {
cell = tableView.dequeueReusableCellWithIdentifier("fooCellIdentifier", forIndexPath: indexPath)
} else {
cell = tableView.dequeueReusableCellWithIdentifier("barCellIdentifier", forIndexPath: indexPath)
}
//customise the cell
return cell!
}
Here is Another Solution which Might help You
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
var cell = tableView.dequeueReusableCellWithIdentifier("relatedTableViewCell") as? relatedTableViewCell
if cell == nil {
let nib:Array = NSBundle.mainBundle().loadNibNamed("relatedTableViewCell", owner: self, options: nil)
cell = nib[0] as? relatedTableViewCell
}
let info = relatedQuizArr.objectAtIndex(indexPath.row) as! relatedInfo
cell?.relatedImage.sd_setImageWithURL(NSURL(string: info.rqImage!))
cell?.relatedLabel.text = (String: info.rqName!)
cell?.rQuizView.layer.shadowOffset = CGSizeMake(-1, 1)
cell?.rQuizView.layer.shadowOpacity = 0.4
return cell!
} else {
var cell = tableView.dequeueReusableCellWithIdentifier("relatedTableViewCell") as? relatedTableViewCell
if cell == nil {
let nib:Array = NSBundle.mainBundle().loadNibNamed("relatedTableViewCell", owner: self, options: nil)
cell = nib[0] as? relatedTableViewCell
}
if indexPath.row == 0 {
let label = UILabel(frame: CGRectMake(22, 10, 200, 40))
label.font = label.font.fontWithSize(25)
label.textAlignment = NSTextAlignment.Center
label.text = "Featured Quiz"
cell!.addSubview(label)
}
else {
}
let info = featuredQuizArray.objectAtIndex(indexPath.row) as! relatedInfo
cell?.relatedImage.sd_setImageWithURL(NSURL(string: info.featuredqImage!))
cell?.relatedLabel.text = (String: info.featuredqName!)
cell?.rQuizView.layer.shadowOffset = CGSizeMake(-1, 1)
cell?.rQuizView.layer.shadowOpacity = 0.4
return cell!
}
Hope It will help.
I am developing news feed and I am using uitableview to display data. I am loading each cell data synchronically in other thread and use protocol method to display loaded data:
func nodeLoaded(node: NSMutableDictionary) {
for var i = 0; i < nodesArray.count; ++i {
if ((nodesArray[i]["id"] as! Int) == (node["id"] as! Int)) {
nodesArray[i] = node
}
}
}
The problem is that when I scroll my uitableview (while data synchronically loading), some of my cells repeats (8 row has same content like first, or 6 row has the same content like second row). When I scroll after some time (I suppose after data is loaded) then all become normal.
I looking for answers and found that I have to check if cell is nill at cellForRowAtIndexPath, but in swift my code is different then in objective C:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: NewsCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewsCell
var node = nodesArray[indexPath.row] as! NSDictionary
if (node["needLoad"] as! Bool) {
dbHelper.getNode(node["id"] as! Int, hash: node["id"] as! Int, tableName: DbHelper.newsTableName, callback: self)
} else {
cell.id = node["id"] as! Int
cell.titleLabel.text = node["title"] as? String
cell.descriptionLabel.text = node["description"] as? String
cell.imgView.image = WorkWithImage.loadImageFromSD((node["image"] as! String))
}
return cell
}
Also I can't check if cell == nil bcs of binary error (NewsCell can't be nil).
What should I do? Thx.
you seem to have created a separate class for UITableViewCell. The problem with your code is that you are not resetting the labels when reuse happens.
Oveeride prepareForReuse method in your custom UITableviewCell class and reset your interfaces there. That should fix the issue.