Table view cell get initial value after scrolling - swift

I have a table view and 10 different prototype cell. I used storyboard and created custom UITableCell class for each cell.
There is a checkbox in CheckBoxCell. I created these checkboxes in loop according to options count.
Problem is that after I checked a checkbox, checkbox value changes but when I scroll up or down the table view, checkbox value changes with inital value.
I investigated some questions in stackoverflow. I live this problem, because of after every scroll dequeReusebleCell works and re-create the cell in the queue. I tried to use these solutions, but I cannot succeeded.
I am new for Swift and I don't know how can I solve this problem.
May someone tell me how can I solve this problem and what is the correct approach?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = surveyDetailArray.first!.elements[indexPath.row]
switch object.type {
case CellConfig.checkbox.rawValue:
let cell = tableView.dequeueReusableCell(withIdentifier: "CheckboxCell", for: indexPath) as! CheckboxCell
let object = surveyDetailArray.first!.elements[indexPath.row]
for label in object.options {
cell.checkbox = BEMCheckBox()
cell.checkbox.onAnimationType = .bounce
cell.checkbox.offAnimationType = .bounce
cell.checkbox.boxType = .square
cell.checkbox.onFillColor = .red
cell.checkbox.offFillColor = .white
cell.checkbox.onCheckColor = .white
cell.checkbox.delegate = self
cell.checkbox.tag = label.id
cell.contentView.addSubview(cell.checkbox)
}
return cell
}

In your problem the cell is dequeuing and is getting back to the old state, that's will obviously happen because of dequeReusableCell,
now for the solution, you should use a model to store the states of different checkboxes and on cellforRowAt
add the code for checkbox persistance according to the model, when you enable a checkbox,
change the value of the variable in the model also and leave rest it on your cellForRowAt code. I'm adding a small example for your understanding, hope it helps.
CODE
Struct ButtonsStates {
var isButtonEnabled : Bool = false
}
// In your ViewController use the above model for saving buttonValues
var buttonStates : [ButtonStates]? // initialize as many as the rows
// in cellForRowAt
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ExampleCell", for: indexPath) as? ExampleCell else { return fatalError("") }
// here if the cell is dequeued still when cell will again be visible then this condition will be checked
cell.customButton.isSelected = buttonStates[indexPath.row].isButtonEnabled
return cell

Related

Change colour of like button in tableview cell

I am trying to change the color of the like button in my table view cell to red when the users taps on the like button for a specific post. But at the moment when i click like for one post, all the other post like buttons turn red too.
func didTapLike(_ sender: UIButton) {
if let indexPath = getCurrentCellIndexPath(sender) {
clickedLike = indexPath
like()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
if let indexPath = clickedLike {
// change like button to red
let selectedRow = indexPath.row
cell.HeartButton.setImage(UIImage(systemName: "heart.fill"), for: .normal)
cell.HeartButton.tintColor = UIColor.red
cell.LikeCount.textColor = UIColor.red
}
}
}
You have to understand the principle of reusable cells. What is happening is the cells that you have liked are being re-used to the cells that you never liked in the first place. Thus, it looks as if you liked everything because property of the cells that you never liked never had their properties reset.
The same cell maybe be reused with the same properties later on even though you have set its properties at the cellForRowAt function which is why you may see that there's no changes. Thus, you have to specifically reset its properties every time it is reused.
This can be done by overriding the prepareForReuse()inside the cell and setting the properties to its default values again. For example, you may want to try the following in your PostTableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
//set it all back to the default values (in this case, heart and black color tint)
self.HeartButton.setImage(UIImage(systemName: "heart"), for: .normal)
self.HeartButton.tintColor = UIColor.black
self.LikeCount.textColor = UIColor.black
}

Realm results object with incorrect results (iOS, Swift)

I am having issues getting Realm's result object to be accessed correctly using UITableView's cellForRowAt.
Here's the setup:
UITableViewController is divided into sections based on the Objects's category (a string defined in the object).
UITableViewController has a segue to a UIViewController which takes form input. That view controller writes to Realm and then makes a call back via delegation to refresh the table view data.
When that screen dismisses and returns to the UITableViewController, as I try to add the row via category, I am getting empty objects. However, when I use a for loop within cellForRowAt, I can access the data.
Here is what I'm running in this section:
func loadItems() {
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category")
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let categoryName = categories.categories[indexPath.section]
let currItem = itemsList[indexPath.row]
if currItem.item_category == categoryName {
cell.textLabel!.text = currItem.item_name
}
return cell
}
It seems to be evaluating category correctly and stepping into that block, but the object's item_name and item_category is null. Here is a screenshot of the debugger inside the if statement:
Debugger Image
Is there something I need to change with how I'm using the object, pulling data, etc, to get the data correct in the object?
Found my answer here: UITableView with Multiple Sections using Realm and Swift
This is the change I made to cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let currItem = itemsList.filter("item_category = '\(categories[indexPath.section])'")[indexPath.row]
cell.textLabel!.text = currItem.item_name
return cell
}
The issue I was having was I was constantly pulling the first position results object and not the first position within a section. I needed to narrow down to my section and then pull the first row.
I think it may have to do with your filters predicate. Try changing
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category"
to this
itemsList = try! Realm().objects(Items.self).filter("list_id = '\(list_id)'").sorted(byKeyPath: "item_category"
Add single quotations around '(list_id)'.
Realm Filtering

UITableViewCell title positioning

I am creating an app with UITableViewCells that expand when selected. However, when the cell expands, the title (and subtitle) are moving downwards to stay centered vertically in the cell. I'd like them to stay where they were originally but can't figure out how to do it.
I have tried setting the frame manually with cell.textLabel?.frame = myRect, but this does nothing. I am unable to find any constraints, as print(cell.textLabel?.constraints) returns optional([]).
Here are my normal cells and my expanded cells.
My code for creating the cells:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
if cell == nil {
cell = UITableViewCell(style: .value1, reuseIdentifier: "reuseIdentifier")
}
cell!.textLabel?.text = content[indexPath.row]
cell!.clipsToBounds = true
cell!.selectionStyle = .none
cell!.detailTextLabel?.text = detailContent[indexPath.row]
return cell!
}
Any help would be greatly appreciated.
You are using a standardized cell, the title and detail are fixed, i think you need make a custom cell for your project
Look this project I make a simple test with customized cell
https://github.com/TexugoProgramador/Teste-Celula-Customizada

UITableViewCell's attributed text not updating

I have a table view that is connected through a Core Data's NSFetchedResultsControler. The cells have a UILabel with attributed text and a UISwitch representing the object's isEnabled value. Inside the cell, I have a function:
func configure(with viewModel: CellViewModel) {
myCustomLabel.attributedText = viewModel.attributedText // NSMutableAttributedText
}
Chaning the switch changes the isEnabled value on the object, which in cause raises the NSFetchedResultsControler, which calls:
let cell: MyTableViewCell = tableView.dequeueReusableCell(forIndexPath: indexPath) //my custom helper method
let cellViewModel = viewModel.itemModel(at: indexPath)
cell.configure(with: cellViewModel)
I do not want to use reloadData, reloadRows or reloadSections, because I want to keep animations while changing the cell's state and avoid cell fading animation if only switch is flipped. I know this is quite bad approach but it's required to keep the animations.
The problem is: The attributedText doesn't change on the label immediately. Only after cell is reused. The debugger displays the new value of the label correctly. I have tried:
this suggestion (setting font and color to nil beforehand),
calling setNeedDisplay on both the label and it's superview
setting the non-attributed text first, then attributedText
Nothing works. Do you have any ideas why it's like that?
System is iOS 11.4, XCode 9.4.1.
If you wrap myCustomLabel.attributedText = viewModel.attributedText in a DispatchQueue.main.async { } it will work:
DispatchQueue.main.async {
myCustomLabel.attributedText = viewModel.attributedText
}
I think that instead of:
let cell: MyTableViewCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
You should use:
tableView.cellForRow(at: indexPath)
That will return you the cell instance for that indexPath. By calling "dequeueReusableCell" you are essentially saying you want a new Cell.
That assumes that I get what you are trying to achieve and your snippet is not from the
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
method.
If that doesn't work might be worth adding a git link to take a look at your code.
Hope that helps!

Dynamically add subViews to UITableViewCell

I need to add UILabels to UITableViewCell, but its dynamic, first cell can have 1 label, second can have 4 and I dont know before hand. So I tried this
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell: ReviewTableViewCell = reviewTableView.dequeueReusableCell(withIdentifier: "Review", for: indexPath) as! ReviewTableViewCell
var reviewObj:Review!
reviewObj = reviewArray[(indexPath as NSIndexPath).row]
let viewsAdded = commentViewsAddedDict[indexPath.row]
if(viewsAdded == nil)
{
for comment in reviewObj.commentArray
{
let label1 = UILabel()
label1.text = “text1”
label1.textColor = UIColor(hexString: "#333333")
let label2 = UILabel()
label2.text = “text2”
label2.numberOfLines = 0
label2.sizeToFit()
label2.textColor = UIColor(hexString: "#666666")
let label3 = UILabel()
label3.text = "----------------------------------------------------------------------"
label3.textColor = UIColor(hexString: "#eeeeee")
cell.stackView1.addArrangedSubview(label1)
cell.stackView1.addArrangedSubview(label2)
cell.stackView1.addArrangedSubview(label3)
}
commentViewsAddedDict[indexPath.row] = true
}
return cell
}
But what happens, the previously added views are not removed and it again tries to add new views.
So I want to know, what is the efficient way to do this.
Secondly, where I am going wrong.
Regards
Ranjit
You are using commentViewsAddedDict to figure out whether rows have been added or not. But whether these labels were added or not is not a function of the row in the table, but rather of the cell, which is reused.
So, I'd would advise:
eliminate this commentViewsAddedDict logic; and
move the logic regarding how many labels have been added to ReviewTableViewCell.
So, you might end up with:
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = reviewTableView.dequeueReusableCell(withIdentifier: "Review", for: indexPath) as! ReviewTableViewCell
var reviewObject = reviewArray[(indexPath as NSIndexPath).row]
cell.updateLabels(for: reviewObject)
return cell
}
And in ReviewTableViewCell:
func updateLabels(for reviewObject: ReviewObjectType) {
// add label if needed
// update label `text` if needed
// remove any labels that need to be removed
}
It's a little hard to be specific on the logic in updateLabels as the provided code snippet in the question is unclear, but the basic idea is that the ReviewTableViewCell should keep track of whether its labels have been added or not, and on the basis of the reviewObject, decide whether it needs to add a label, update an existing label, or remove any labels not needed for this particular reviewObject. But all of this "label state" logic is a function of the cell (which can be reused), not of which row in the table to which the cell corresponds.