Change colour of like button in tableview cell - swift

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
}

Related

Swift Firestore, "Like" feature not working with button in table view cell

I am trying to implement a "Like" functionality to my app. I have a "recipes" with a field in each called "likes". In each table view cell I have a "heart" button which when the user clicks it should increment the likes by 1. I have got it to the point it increments the field but not for the correct recipe. for example if I click the like button on the first recipe it might increment the likes field for the 3rd recipe. Can anyone help with incrementing the correct document.
//Function for incrementing likes
#objc func likesPressed() {
if User.currentUser() != nil{
FirebaseReference(.RecipePosts).document(recipeToLike.id).updateData([
"likes": FieldValue.increment(Int64(1))
])
self.hud.textLabel.text = ""
self.hud.indicatorView = JGProgressHUDImageIndicatorView(image: UIImage(systemName: "heart.fill")!)
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
}else{
self.hud.textLabel.text = "Please Login To Your Account!"
self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 3.0)
}
}
//Table View cell for row at
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = LatestRecipeTV.dequeueReusableCell(withIdentifier: "latestCell", for: indexPath) as! SocialFeedTableViewCell
cell.configureCell(recipeArray[indexPath.row])
cell.cellDelegate? = self
//Getting the recipe to like
recipeToLike = recipeArray[indexPath.row]
//"Like" Button action in Cell
cell.likesBtn.addTarget(self, action: #selector(likesPressed), for: .touchUpInside)
return cell
}
Right now, your recipeToLike is going to get set many times while the table is populated with data. This is because cellForRowAt gets called when the cell gets created -- not when the button is tapped.
In order to figure out which recipe ID you should be sending, you'll need to have a way to figure out which button was tapped. A common way to do this is by assigning a tag to the button.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = LatestRecipeTV.dequeueReusableCell(withIdentifier: "latestCell", for: indexPath) as! SocialFeedTableViewCell
cell.configureCell(recipeArray[indexPath.row])
cell.cellDelegate? = self
cell.likesBtn.tag = indexPath.row //<-- Here
//"Like" Button action in Cell
cell.likesBtn.addTarget(self, action: #selector(likesPressed), for: .touchUpInside)
return cell
}
Then, in your likesPressed function, you'll need to read that tag:
#objc func likesPressed(sender: UIButton) { //<-- Here
let recipeToLike = recipeArray[sender.tag] //<-- Here
if User.currentUser() != nil{
FirebaseReference(.RecipePosts).document(recipeToLike.id).updateData([
"likes": FieldValue.increment(Int64(1))
])
self.hud.textLabel.text = ""
self.hud.indicatorView = JGProgressHUDImageIndicatorView(image: UIImage(systemName: "heart.fill")!)
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
}else{
self.hud.textLabel.text = "Please Login To Your Account!"
self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 3.0)
}
}
Since you'll be defining recipeToLike locally within likesPressed, you can get rid of the original recipeToLike property on your class.

CollectionViewCell: Access to data of a cell through indexPath?

What I have:
I have a CollectionViewCell as .xib + .swift files.
Every cell gets their data from the database.
Inside every cell I have a like button.
What I want:
When I press the like button of a certain cell, I want to be able to read this data so I can change it and write the new data in the Database. So I want to change the like attribute of the dataset of a certain cell and save it in the DB
What I tried:
I have the indexPath of the cell but how can I read the data of the cell?
#IBAction func likeButton(_ sender: UIButton) {
var superview = self.superview as! UICollectionView
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:superview)
let indexPath = superview.indexPathForItem(at: buttonPosition)
print(superview.cellForItem(at: indexPath!))
// Change picture
if sender.currentImage == UIImage(systemName: "heart.fill") {
sender.setImage(UIImage(systemName: "heart"), for: .normal)
} else {
sender.setImage(UIImage(systemName: "heart.fill"), for: .normal)
}
}
UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier,
for: indexPath) as! MyCollectionViewCell
cell.backgroundColor = .white
let newShoeCell = shoesArray?.randomElement()
// Fill cells with data
cell.imageView.image = UIImage(named: newShoeCell!.imgName)
cell.shoeTitle.text = newShoeCell!.title
cell.price.text = String(newShoeCell!.price)
cell.ratingNumberLabel.text = String(newShoeCell!.meanRating)
cell.floatRatingView.rating = newShoeCell!.meanRating
if newShoeCell!.liked {
cell.likeButtonOutlet.setImage(UIImage(systemName: "heart.fill"), for: .normal)
} else {
cell.likeButtonOutlet.setImage(UIImage(systemName: "heart"), for: .normal)
}
return cell
}
You need to change your thinking. It is not the data "of a cell" A cell is a view object. It displays information from your data model to the user, and collects input from the user.
You asked "...how can I read the data of the cell?" Short answer: Don't. You should be saving changes into your data model as you go, so once you have an index path, you should use it to index into your data model and get the data from there.
You need to figure out which IndexPath the tapped button belongs to, and fetch the data for that IndexPath from your data model.
If you look at my answer on this thread I show an extension to UITableView that lets you figure out which IndexPath contains a button. Almost the exact same appraoch should work for collection views. The idea is simple:
In the button action, get the coordinates of the button's frame.
Ask the owning table/collection view to convert those coordinates to
an index path
Use that index path to fetch your data.
The only difference is that for collection views, the method you use to figure out which IndexPath the button maps to is indexPathForItem(at:) instead of indexPathForRow(at:)
I suggest you add a property to your cell
class MyCell: UITableViewCell {
var tapAction: (() -> Void)?
...
#IBAction func likeButton(_ sender: UIButton) {
tapAction?()
...
}
}
and set it in view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.tapAction = { [weak self] in
// call to database or whatever here and then reload cell
tableView.reloadRows(at: [indexPath], with: .automatic)
...
}
In general, cell should never care what its indexPath is, nor should it make calls to superview

Swift tableview cell doesn't save the state with different identifier

I am very confused on the reuse of the cells.
I have a table, each cell is a cell with a switch on it. If I toggle a switch I set the background color of that cell to a different color. However every time I scroll these changes don't persist.
I am subclassing UITalbeViewCell to create my own custom cell. Each cell has a different identifier. However when I scroll through the table, whatever changes I made to the cell still doesn't save. I've read similar questions but none of them worked.. Some suggested subclass which I did, some suggested use different identifier which I also did...
Here is the code of my tableview.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let key = Array(dataSource[indexPath.section].keys)[indexPath.row]
let cell = CellWithSwitch.init(style: .subtitle, reuseIdentifier: key)
cell.awakeFromNib()
let val = Array(dataSource[indexPath.section].values)[indexPath.row]
cell.switchView?.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
if let index = key.firstIndex(of: "."){
cell.textLabel?.text = String(key.suffix(from: key.index(index, offsetBy: 1)))
}else{
cell.textLabel?.text = key;
}
cell.switchView?.setOn(val, animated: true)
return cell
}
You can change array value in switchChange action
lets i take array for switch as below:
var arrSwitch = [false,false,false,false,false,false,false,false,false,false]
Below is my cellForRowAt Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! customCell
cell. switchView.setOn(self.arrSwitch[indexPath.row], animated: false)
cell. switchView.tag = indexPath.row
cell. switchView.addTarget(self, action: #selector(self.onSwitchTap(_:)), for: .valueChanged)
return cell
}
Here is my onSwitchTap Action
#IBAction func onSwitchTap(_ sender: UISwitch) {
self.arrSwitch[sender.tag] = !self.arrSwitch[sender.tag]
}
Now on scroll it will persist last changes you have done.

IOS TableView how can I update an element in my TableView

I am using Swift 4 and I finally got my TableView cell working correctly . I have a like button and when a user clicks it I update only the cell that was clicked and show a new image either like or unliked, in addition I show a LikeCounter +1 for like and -1 for unlike .That's all working.
My question is how can I make it so that I only update those 2 elements on button clicked ?
right now it updates the Table View Cell . This is my code
#IBAction func voteAction(_ sender: UIButton)
{
if let indexPathm = TableView.indexPathForView(sender) {
if streamsModel.VoteStatus[sender.tag] == "0" {
streamsModel.VoteStatus[sender.tag] = "1"
streamsModel.Votes[sender.tag] = streamsModel.Votes[sender.tag] + 1
} else {
streamsModel.VoteStatus[sender.tag] = "0"
streamsModel.Votes[sender.tag] = streamsModel.Votes[sender.tag] - 1
}
TableView.reloadRows(at: [indexPathm],with: .none)
}
}
Once you click that button then it goes here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath) as! HomeTVC
cell.vote_status = streamsModel.VoteStatus[indexPath.row]
if streamsModel.VoteStatus[indexPath.row] != "0" {
cell.voteImage.setImage(UIImage(named: "liked"), for: .normal)
} else {
cell.voteImage.setImage(UIImage(named: "unliked"), for: .normal) }
}
I have a lot more cell elements in that TableViewCell however I would only like to update and isolate that specific piece of code in my TableViewCell .
I have tried doing it using this method Update label in custom UITableView Cell however I find that if I scroll down and then back up it reverts whatever changes I did .
// This works however if you scroll down then back up
// it reverts back to the original status
let button = sender as! UIButton
let view = button.superview!
let cell = view.superview as! HomeTVC
let indexPath = tableView.indexPathForCell(cell)
println(indexPath)
if(indexPath != nil){
if streamsModel.VoteStatus[indexPath.row] != "0" {
cell.voteImage.setImage(UIImage(named: "liked"), for: .normal)
} else {
cell.voteImage.setImage(UIImage(named: "unliked"), for: .normal) }
}
}
So now I'm calling but it's updating the whole cell .
TableView.reloadRows(at: [indexPathm],with: .none)
You can retrieve the cell and update only the properties of the cell directly like this:
if let cell = tableView.cellForRow(at: indexPath) as? HomeTVC {
// Update the cell
}
EDIT:
I missed the second part of the question, sorry. I think that the problem here is that you're not updating your model, so when you scroll up the delegate calls tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) and sets the value as saved previously on the model. You have to update the value of streamsModel.VoteStatus when the button is pressed.

Failed to compare images, one from a UIButton

I know how to get the image of a button, but it doesn't seem to work here:
#IBAction func checkButtonClicked(_ sender: UIButton) {
if sender.image(for: .normal) == #imageLiteral(resourceName: "btn_check_off_normal_holo_light") {
sender.setImage(#imageLiteral(resourceName: "btn_check_on_focused_holo_light").withRenderingMode(.alwaysOriginal), for: .normal)
} else {
sender.setImage(#imageLiteral(resourceName: "btn_check_off_normal_holo_light").withRenderingMode(.alwaysOriginal), for: .normal)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "UserCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? UsersTableViewCell else {
fatalError("The dequeued cell is not an instance of SelectTableViewCell.")
}
cell.checkButton.setImage(#imageLiteral(resourceName: "btn_check_off_normal_holo_light").withRenderingMode(.alwaysOriginal), for: .normal)
return cell
}
I set the button image in cellForRowAt(the button is in a cell in a dynamic tableview. In the simulator, I can see that the image is being set. However, this piece of code doesn't run:
if sender.image(for: .normal) == #imageLiteral(resourceName: "btn_check_off_normal_holo_light") {
sender.setImage(#imageLiteral(resourceName: "btn_check_on_focused_holo_light").withRenderingMode(.alwaysOriginal), for: .normal)
}
Setting a breakpoint tells me that Xcode skips right over the code inside the if statement: even though the image clearly has been set to the one in the if statement.
I wonder if I am missing something really simple here?
Your general approach here is incorrect. Instead of attempting to check the button's current image as a way to toggle the image, you should have a flag as part of your data model used to populate the table view.
This flag is needed in your cellForRowAt so you know which image to show on each cell (cells get reused as you scroll).
Then your checkButtonClicked should simply toggle the flag for the given row and then tell the table view to reload that one row.