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.
Related
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
}
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.
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
I have a chat message table view with two cells to display, depending on whom sent the message.
I want the last cell to display the time, and only the last one. When I use tableView(:willDisplay cell:forRowAt indexPath:), the last cell doesn't show anything...
How can I display the time on that last cell?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if chatBubbles[indexPath.row].user == UserDefaultsService.shared.userID {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.senderCellIdentifier.rawValue, for: indexPath) as! SenderTVC
populateSenderChatBubble(into: cell, at: indexPath)
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.conversationCellIdentifier.rawValue, for: indexPath) as! ConversationTVC
populateConversationChatBubble(into: cell, at: indexPath)
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == chatBubbles.count - 1 {
// What to do in here to display the last cell time?
}
}
Here is the method that display the cell content:
func populateSenderChatBubble(into cell: SenderTVC, at indexPath: IndexPath) {
let bubble = chatBubbles[indexPath.row]
let isoDateString = bubble.date
let trimmedIsoString = isoDateString.replacingOccurrences(of: StaticLabel.dateOccurence.rawValue, with: StaticLabel.emptyString.rawValue, options: .regularExpression)
let dateAndTime = ISO8601DateFormatter().date(from: trimmedIsoString)
date = dateAndTime!.asString(style: .short)
time = dateAndTime!.asString()
if dateAndTime!.isGreaterThanDate(dateToCompare: Date()) {
dateToShow = "\(date!) \(time!)"
}
else {
dateToShow = "\(time!)"
}
cell.senderDateLabel.text = dateToShow
cell.senderConversationLabel.text = bubble.content
}
The cell doesn't know it's last unless you tell it, but the tableView does know who's last. With that in mind, I would add a boolean in your cell like this:
var isLastCell: Bool = false {
didSet {
// do stuff if it's the last cell
if isLastCell {
// configure for isLastCell
} else {
// configure it for !isLastCell
}
}
}
When your custom UITableViewCell class initializes, it'll be with isLastCell = false, so assume that in your configuration. Whenever the boolean is updated to true, the cell will update via the didSet.
Then, in your cellForRow method, test the indexPath to see if it's the last indexPath of the datasource, if so, cell.isLastCell = true and the didSet in the cell will trigger to do whatever adjustments you need to do.
Another thing you'll need to do with this implementation is use cellForRow to update isLastCell for not just the last cell, but the cells that aren't last, since cells are created and destroyed all the time and the last cell at one moment might not be the last cell in another.
I have a tableview that contains a button in the left of cell. the run works well and when i select a button a made it change its image background. The problem is when I select a buttonButtonOne selection , it select other buttons when I scroll tableview down, Other buttons selected my code is this way:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableview.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PlayerCell
cell.BtnAdd.tag = indexPath.row
cell.BtnAdd.addTarget(self, action: #selector(CreateTeamTwo.PlayerAdded(_:)), forControlEvents: .TouchUpInside)
return cell
}
func PlayerAdded(sender: UIButton){
// let buttonTag = sender.tag
sender.setImage(UIImage(named: "btn_buy_minus.png"), forState: UIControlState.Normal)
}
can you help me please ?
Take one Array to store selected indexPaths Buttons tags. and in selector methods store all the sender tags and reload the tableView. and in cellForRow method check if current indexpath is contain in selected array then show minus image otherwise show plus image.
var selected = [Int]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableview.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PlayerCell
cell.BtnAdd.tag = indexPath.row
cell.BtnAdd.addTarget(self, action: #selector(CreateTeamTwo.PlayerAdded(_:)), forControlEvents: .TouchUpInside)
if selected.contains(indexPath.row) {
cell.btn.setImage(UIImage(named: "btn_buy_minus.png"), forState: .Normal)
} else {
// here set the plus image
cell.btn.setImage(UIImage(named: "btn_buy_plus.png"), forState: .Normal)
}
return cell
}
func PlayerAdded(sender: UIButton){
selected.append(sender.tag)
self.tableView.reloadData()
}