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
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 two tableviews with two different custom tableview cells.. and am doing this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.tableview {
let cell = tableview.dequeueReusableCell(withIdentifier: "matchescell") as! MatchesTableViewCell
// ......
return cell
} else {
let cell = tableview.dequeueReusableCell(withIdentifier: "filtercell") as! FilterTableViewCell
// ......
return cell
}
}
and of course i have registered them both from storyboard...
but i keep getting this:
on this line:
let cell = tableview.dequeueReusableCell(withIdentifier: "filtercell") as! FilterTableViewCell
tried to register them like this:
tableview.register(MatchesTableViewCell.self, forCellReuseIdentifier: "matchescell")
filterdrop.register(FilterTableViewCell.self, forCellReuseIdentifier: "filtercell")
but still getting the same error!
What am doing wrong?!
The problem is that you typed "tableview" instead of "tableView" in your else part:
replace :
let cell = tableview.dequeueReusableCell(withIdentifier: "filtercell") as! FilterTableViewCell
with :
let cell = tableView.dequeueReusableCell(withIdentifier: "filtercell") as! FilterTableViewCell
It is crashing because your "tableview" doesn't have this cell registered.
Add the "filtercell" identifier in the storyboard or xib custom cell (FilterTableViewCell).
Possible cause:
You haven't registered the class or the nib of the cell to the proper tableview
You haven't set the identifier in the cellview in you nib/storyboard
You have inverted the tableview and their cell type
This because you either didn't register the cell
tableView.register(CustomCellClass.self, forCellReuseIdentifier: "CellID")
//
tableView.register(UINib(nibName: "CustomCellClass", bundle: nil), forCellReuseIdentifier: "CellID")
or you swapped the dequeuing line for the tables
Have a look into your Tableview controller in the Storyboard. You should have another indentier than called in:
let cell = tableview.dequeueReusableCell(withIdentifier: "filtercell") as! FilterTableViewCell
BTW, you should always use this newer function
dequeueReusableCell(withIdentifier:for:)
instead of
dequeueReusableCell(withIdentifier:)
See this post for details.
You can easily solve this problem just to check what tableview you use at that time.
you can try this code,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) {
if (tableview == (yourtableview 1)) {
let cell = yourtableview 1.dequeueReusableCell(withIdentifier: "yourtableview Cell 1") as? yourtableview Cell 1
// write your code for tableview 1
return cell
}
else
{
let cell = yourtableview 2.dequeueReusableCell(withIdentifier: "yourtableview Cell 2") as? yourtableview Cell 2
// write your code for tableview 2
return cell
}
}
I add a cell with a label in it in a new section on the top of the tableView as section 0 and i show and hide this section according to what type of data i'm displaying.
It works fine when there is no data in the hashtag type posts then when there is hashtag data to be displayed in the array like two or three items it works fine and the top section 0 cell is displayed then when i scroll down and up again i get an error in the AppDelegate after trying to return the top section cell.
I know the question is a little bit complicated but what i'm trying to achieve is to display and hide a cell on the top of my feed according to the type of data i'm displaying in my tableview. If hashtag news feed data then show the top cell in section 0 if showing ordinary news feed in the tableview then return only one section and don't load the top section with the cell inside of it.
By the way i'm displaying the cell as a Nib. And declaring it in the viewDidLoad
let reloadNib = UINib(nibName: "ReloadTableViewCell", bundle: nil)
feedTableView.register(reloadNib, forCellReuseIdentifier: "reloadCell")
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102a772a0)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
switch indexPath.section {
case 0:
if hashPostsOnly {
let reloadCell = tableView.dequeueReusableCell(withIdentifier: "reloadCell", for: indexPath) as! ReloadTableViewCell
return reloadCell // ERROR AFTER RETURNING CELL
} else {
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
}
case 1:
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
default:
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
if hashPostsOnly {
return 2
} else {
return 1
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if hashPostsOnly {
return 1
} else {
return feeds.count
}
} else {
return feeds.count
}
}
Here is a screen shot of what i'm achieving but when i scroll down then up it reloads the top section cell "Reload Feeds" and then error.
Since there are just two sections and you had duplicate code, things can be simplified to:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.section == 0 && hashPostsOnly
{
let reloadCell = tableView.dequeueReusableCell(withIdentifier: "reloadCell", for: indexPath) as! ReloadTableViewCell
return reloadCell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
}
}
I can't be 100% sure without knowing the exact error you're getting or knowing if there are other issues in the code elsewhere causing this, but:
As a general rule, dequeuing twice from a table view and returning a single cell does bad things in weird and mysterious ways. Refactor your code to only deuque a regular cell when you need it and not to do so when you're showing the refresh button
I've got problems when I scroll down in my UITableview. The table shows me cells with old content when the cell is reused.
The Probleme is the following:
Swift wants to reuse an old cell, but doesn't properly clear the old content from the old cell. This leads to cells with old content, although I'm providing new data to the cells.
Architecture of the UITableView if the following:
Each custom cell has their own identifier
Each custom cell is separated in an own class
Screenshots of the problem:
Beginning of the Questionnaire Screen Shot:
The scrolled down table:
The problem here is the "Handedness"-Cell which is showing the cell number 3 (because of the reuse of the cell), which is not right
The numberOfSection-Method
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
The numberOfRowsInSection-Method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(section == 0){
return questionnaireStructure.count
} else {
return 1
}
}
The cellForRowAt-Method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// first section is the normal Questionnaire
if(indexPath.section == 0){
// current questionnaireStructure
let questStruct:QuestionnaireStructure? = questionnaireStructure[indexPath.row]
// current cell is a "Headline"
if(questStruct?.elementtype == "elements/headlines"){
let cell = tableView.dequeueReusableCell(withIdentifier: "HeadlineStructureCellID", for: indexPath) as! Headline
cell.headline.text = questStruct?.headline
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.elementtype == "elements/texts"){
// current cell is a "texts"
let cell = tableView.dequeueReusableCell(withIdentifier: "TextsStructureCellID", for: indexPath) as! Texts
cell.textsLabel.text = questStruct?.text
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.questiontype == "Slider"){
// currrent cell is a "slider-Question"
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSliderStructureCellID", for: indexPath) as! Slider
cell.sliderQuestion.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
let values = (questStruct?.values)!
let valueArray = values.array as! [Values]
cell.slider.minimumValue = Float(valueArray[0].min)
cell.slider.maximumValue = Float(valueArray[0].max)
let answers = (questStruct?.answers)!
let answerArray = answers.array as! [Answers]
cell.minLabel.text = answerArray[0].label
cell.maxLabel.text = answerArray[1].label
return cell
} else if(questStruct?.questiontype == "SingleChoice"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
let radioButtonController = SSRadioButtonsController()
radioButtonController.delegate = self
radioButtonController.shouldLetDeSelect = true
cell.radioButtonController = radioButtonController
cell.updateCellData(questStruct: questStruct!, indexInTable: indexPath.row)
return cell
} else if(questStruct?.questiontype == "MultipleChoice"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionMultipleChoiceStructureCellID", for: indexPath) as! MultipleChoiceCell
cell.multQuestionLabel.text = questStruct?.question
cell.questStruct = questStruct
return cell
} else if(questStruct?.questiontype == "YesNoSwitch"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionYesNoSwitchStructureCellID", for: indexPath) as! YesNoSwitch
cell.yesNoQuestion.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.questiontype == "TextDate"){
let cell = tableView.dequeueReusableCell(withIdentifier: "Datepicker", for: indexPath) as! DatePicker
cell.question.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
//cell.singleChoiceLabel.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
} else {
//last section is the save button
// show the save button when the Questionnaire is loaded
if(questionnaireStructure.count != 0){
let cell = tableView.dequeueReusableCell(withIdentifier: "SaveStructureCellID", for: indexPath) as! SaveQuestionnaire
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "TextsStructureCellID", for: indexPath) as! Texts
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
}
}
What I checked:
the data of "questStruct" is providing the latest data
overriding the "prepareForReuse"-Methode without success
Here:
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
//cell.singleChoiceLabel.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
You need to "reset" the cell in case it's being reused. Options are:
write a reset() function in the cell, to clear any assigned data and display "default" content, or
create an empty questStruct and call cell.updateCellData(questStruct: questStruct!, indexInTable: indexPath.row)
Option 1. is probably the easiest and most straight-forward.
Are you sure the data isn't actually duplicated in the questStruct array? If that's not the case then all I can think is that it looks like you have two places where a single choice cell is used. In one of them you set a bunch of data, while in the other one you don't seem to set any data. I'm talking about that last else statement where you have the part where you set singleChoiceLabel.text except it's commented out. If that condition gets hit and it's reusing a cell that was configured for the other singleChoiceStructure branch of the if condition then the information will still be filled out from the previous configuration. It's possible the questionType property of one of your QuestionnaireStructure objects is either spelled incorrectly or just a value you haven't accounted for, which is causing the if statement to hit the else which returns an unconfigured QuestionSingleChoice cell that might still have information from the last time it was used.
This question follows up from this: Use UICollectionViews to create dynamic and multiple features.
I am able to create a static cell which displays the name and image of the recipe similar like this app:
Where I am stuck is creating a dynamic row which changes based on the amount of data inside i.e. utensils or nutritional values like the image below:
I know how to display rows of data on tableView normally. But not sure how to embed it into a section inside a tableView. I attempted to add multiple prototype cells and assign them to a subclass of UITableViewCell's. Then I try to use if statements in my cellForRow but this isn't soling my issue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
//set the data here
cell.recipeTitle.text = recipe.name
cell.imageView.image = UIImage(named: "url")
return cell
}
else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! SecondCell
//set the data here
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath) as! ThirdCell
//set the data here
return cell
}
}
I have also looked at this demonstration: https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429, which is near to what I want to achieve but I have found it quite difficult to adapt to it.
If someone could direct me on how best to approach this or a good example then I would really appreciate it.
First you can't have static cells and dynamic cells in the same tableView. So how do you work around that? Define each of the static cells in the sections they belong in as well as the dynamic cells in the sections they belong to. That, however doesn't look like what you are trying to do. You just want multiple sections in the same tableView, each section with a different list of data
To do this you will need the number of sections so use the tableView(_:numberOfSections:) function.
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
You can then(and probably should) give each of those sections a title by initializing an array with the titles in your tableViewController(assuming thats what you are using. It could also just be a tableView).
let headerTitles = ["Nutritional Values", "Utensils", "Ingredients"]
Then use the tableView(_:titleForHeaderInSection:)
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
Now you can start defining your rows by the sections.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 0 {
//Setup up the first row
if indexPath.row == 0 {
//I'm not sure where or how you defined First/SecondCell but this may not work depending on those two questions.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
return cell
} else if indexPath.row == 1 {
let cell = Bundle.main.loadNibNamed("StaticCell", owner: self, options: nil)?.first as! StaticCell
return cell
}
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! SecondCell
//setup cell1 n your storyboard to have a label with a tag # of 12(or whatever number you want to use)
//you also want an array with your utensil data accessible here
let label = cell.viewWithTag(12) as! UILabel
label.text = utensilNames[indexPath.row]
return cell
} else if indexPath.section == 2 {
let cellIngredients = tableView.dequeueReusableCell(withIdentifier: "Ingredients", for: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
return cellIngreidents
}
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
return cell
}
The point here is to use sections then rows to distribute your data.
Just to clarify Section 0 Row 0 - N would be where you're static rows are setup. I found it best to use XIB files subclassing TableViewCell.
Hope this helps.
EDIT So the way I'm looking at the "static" cells is in the first section the xib is the only put exactly where you tell it to be placed. In the example above the first section in the second cell is the