I' m working to an e-commerce app and I have an issue when trying to set the height of a specific row. When I choose the category the table reloads and depending on the category i choosed more or less rows are coming through the api.
So with this setup it s working when i select a specific category but when i choose another it will mess my rows and will increase the height to another
let firstCellHeight:CGFloat = 265
let addBtnCellHeight:CGFloat = 60
let phoneCellHeight: CGFloat = 95
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellIdentifier = self.tableCells[indexPath.row]
let height:CGFloat!
switch cellIdentifier {
case "AddAdImagesCell":
height = self.firstCellHeight
case "AddBtnCell":
height = self.addBtnCellHeight
case "ExtraFieldCell":
if indexPath.row == 4 {
height = self.phoneCellHeight
} else {
height = 44
}
default:
height = 44
}
return height
}
For the cell for row i m having this code:
case "ExtraFieldCell":
let currentField = self.extraFields[indexPath.row - self.firstExtraFieldIndex]
switch currentField.type {
case "select":
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldSelectCell")!
if currentField.name == "area"
return cell
case "text":
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldTextCell") as! FiltersTFCell
cell.label.text = "\(currentField.label):"
return cell
case "checkbox":
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldCheckboxCell") as! CheckboxCell
cell.fieldLabel.text = currentField.label
return cell
default:
let cell = UITableViewCell()
return cell
}
So how to set the height 95 always for the row with the case "text"
try using self.tableView.rowHeight when setting the cell, I tried to apply with your example code below, if you have any questions call me back.
case "ExtraFieldCell":
let currentField = self.extraFields[indexPath.row - self.firstExtraFieldIndex]
switch currentField.type {
case "select":
self.tableView.rowHeight = firstCellHeight
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldSelectCell")!
if currentField.name == "area"
return cell
case "text":
self.tableView.rowHeight = addBtnCellHeight
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldTextCell") as! FiltersTFCell
cell.label.text = "\(currentField.label):"
return cell
case "checkbox":
self.tableView.rowHeight = phoneCellHeight
let cell = tableView.dequeueReusableCell(withIdentifier: "ExtraFieldCheckboxCell") as! CheckboxCell
cell.fieldLabel.text = currentField.label
return cell
default:
self.tableView.rowHeight = 44
let cell = UITableViewCell()
return cell
}
importantly, when is test comment or remove this method
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { ... }
Related
I have the following problem.
example
If a cell has no content, I want to hide the cell. The logic as you can see allows, that constantly 5 cells get returned:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(section == 0){
return 1
}
return 5
}
Here is the logic of the actual table view:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section{
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "CampusGoogleMapsTableViewCell", for: indexPath) as! CampusGoogleMapsTableViewCell
if let building = self.selectedPOIOffice.room?.building{
cell.setMarkerForSelectedBuilding(building)
}
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "CampusTableViewCell", for: indexPath) as! CampusTableViewCell
let iconLabel: UILabel = cell.iconLabel
let titleLabel: UILabel = cell.titleLabel
iconLabel.font = UIFont.fontAwesome(ofSize: 25, style: .solid)
switch indexPath.row{
case 0:
//name
titleLabel.text = selectedPOIOffice.name
titleLabel.textColor = UIColor.black
titleLabel.alpha = 0.8
iconLabel.text = FontAwesomeIcons.University.getIcon()
case 1:
//Phone
titleLabel.text = selectedPOIOffice.phone
titleLabel.textColor = HsKAmpusColors.Red
iconLabel.text = FontAwesomeIcons.Phone.getIcon()
case 2:
//email
titleLabel.text = selectedPOIOffice.email
titleLabel.textColor = HsKAmpusColors.Red
iconLabel.text = FontAwesomeIcons.Mail.getIcon()
case 3:
//Opening Hours
if(titleLabel.text == nil){ break}
titleLabel.text = selectedPOIOffice.openingHours
titleLabel.textColor = UIColor.black
titleLabel.alpha = 0.8
iconLabel.text = FontAwesomeIcons.Clock.getIcon()
case 4:
//Location
titleLabel.text = selectedPOIOffice.room?.roomAndBuildingString ?? ""
titleLabel.textColor = UIColor.black
titleLabel.alpha = 0.8
iconLabel.text = FontAwesomeIcons.PositionMarker.getIcon()
default:
break
}
return cell
default:
return UITableViewCell()
}
}
I think I could solve it with a simple for-statement to check if every cell has any content. Can you help me please with the application?
You are going about this all wrong. cellForRowAt is not the place to attempt to hide a cell. By the time it is called, the cell is going to be shown.
Do one of two things:
Update your data model used by your data source methods to only include the data you want to display. Or...
Implement heightForRowAt to return 0 for rows you don't wish to see.
"If a cell has no content, I want to hide the cell."
That sentence shows a basic misunderstanding of how table views and collection views work. Table views display tabular data from a data model. If you have empty entries in your model, remove them from the model before giving it to the table view.
I have a UITableView that gets reloaded if a button in some of its cells gets tapped. The issue appears if the following steps are made:
Tap a button on a cell so that another cells appear below the tapped cell on reloadData.
Scroll up the table view so that it hides some of the upper content.
Tap the button again to hide the cells that were just shown making another call to reloadData.
Then the table view goes up and hides the upper content (the whole first cell and part of the second one). Here is some of the code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if shouldShowImageResolutionOptions && (indexPath.row == 2 || indexPath.row == 3 || indexPath.row == 4) {
return isLastKnownDeviceOrientationLandscape ? 60 : 80
}
if shouldShowImageDisplayOptions && (indexPath.row == 3 || indexPath.row == 4) {
return isLastKnownDeviceOrientationLandscape ? 60 : 80
}
return isLastKnownDeviceOrientationLandscape ? tableView.frame.size.height / 2.5 + 40 : tableView.frame.size.height / 4.5 + 40
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowImageResolutionOptions {
return 6
}
if shouldShowImageDisplayOptions {
return 5
}
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.IntervalCell) as! IntervalCell
cell.selectionStyle = .none
cell.dottedSliderView.contentMode = .redraw
cell.adjustThumbPosition()
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageResolution
cell.choiseLabel.text = LabelTitles.HDResolutioin
cell.onButtonTap = {
self.shouldShowImageResolutionOptions = !self.shouldShowImageResolutionOptions
self.shouldShowImageDisplayOptions = false
self.menuTableView.reloadData()
}
return cell
case 2:
if(shouldShowImageResolutionOptions) {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = Settings.HDResolution
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageDisplay
cell.choiseLabel.text = LabelTitles.EnlargeImage
cell.onButtonTap = {
self.shouldShowImageDisplayOptions = !self.shouldShowImageDisplayOptions
self.shouldShowImageResolutionOptions = false
self.menuTableView.reloadData()
}
return cell
case 3, 4:
if(shouldShowImageResolutionOptions) {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = indexPath.row == 3 ? Settings.HighResolution : Settings.MediumResolution
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = indexPath.row == 3 ? Settings.ShowFullImage : Settings.EnlargeImage
return cell
}
case 5:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageDisplay
cell.choiseLabel.text = LabelTitles.EnlargeImage
cell.onButtonTap = {
self.shouldShowImageDisplayOptions = !self.shouldShowImageDisplayOptions
self.shouldShowImageResolutionOptions = false
self.menuTableView.reloadData()
}
return cell
default:
return UITableViewCell()
}
}
From the UITableView's reloadData method documentation (https://developer.apple.com/documentation/uikit/uitableview/1614862-reloaddata):
The table view’s delegate or data source calls this method when it wants the table view to completely reload its data. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates.
There are dedicated insert/delete rows methods for inserting and deleting:
insertRowsAtIndexPaths:withRowAnimation:(https://developer.apple.com/documentation/uikit/uitableview/1614879-insertrowsatindexpaths)
deleteRowsAtIndexPaths:withRowAnimation: (https://developer.apple.com/documentation/uikit/uitableview/1614960-deleterowsatindexpaths)
So when you refactor your code to use those it should work smoothly and as expected.
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 am trying to populate a UITableView prototype cell with "Facebook status". It has images and videos depending on the response from Facebook server. But I am not able to populate it properly.
I will get the first image from the array. But when I scroll down UITableView and reach the top again, the image disappears. And when I print the value of i it exceeds the count of array soon after the tableview is loaded.
I am adding the edited code here.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows : Int = 0
if section < numberOfRowsAtSection.count{
rows = numberOfRowsAtSection[section]
print(section,rows)
}
return rows
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Section,Row",indexPath.section, indexPath.row)
switch(combinedArray[indexPath.section][indexPath.row]){
case "photo" :
let cell = tableView.dequeueReusableCell(withIdentifier: "ImageDetail", for:indexPath) as! imageCell
if(im < imageDetails.count){
let imageUrl = imageDetails[im]
let url = URL(string:imageUrl)
let data = try? Data(contentsOf: url!)
cell.storyDetailsLbl1.text = photoStoryDetails[im]
print(imageDetails[im])
cell.images1.image = UIImage(data: data!)
}
im = im + 1
return cell
case "video" :
let cell1 = tableView.dequeueReusableCell(withIdentifier: "VideoCell", for: indexPath) as! videoPlayerCell
cell1.videoView.isHidden = false
if vd < videoURLs.count{
print(videoURLs.count)
let urls = videoURLs[vd]
cell1.videoPlayerItem = AVPlayerItem.init(url: urls)
cell1.videoLabel.text = videoStoryDetails[vd]
}
vd = vd + 1
return cell1
case "link":
let cell2 = tableView.dequeueReusableCell(withIdentifier: "LinkCell", for: indexPath) as! linkCell
if ln < linkDetails.count {
cell2.linkLbl.text = linkDetails[ln]
}
ln = ln + 1
return cell2
default:
let cell3 = tableView.dequeueReusableCell(withIdentifier: "StatusCell", for: indexPath) as! statusCell
if st < statusDetails.count{
cell3.statusLbl.text = statusDetails[st]
}
st = st + 1
return cell3
}
}
Tableviews deque already created cells and reuse them to enhance performance. So a cell with the identifier "ImageDetail" could be reused and populated with data from any of your case statements. This will cause oddities like images disappearing when they shouldn't.
One way to solve this is to ensure you are handling each cell property in every case. cell.storyDetailsLbl1.text isn't handled in the photo case. cell.images1.image isn't handled in the link case, etc.
Better would be to define a cell with a unique identifier for each one of your cases. i.e. PhotoCell, LinkCell, VideoCell, etc.
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.