Swift: Set Variables for TableViewCell - ios

I have a tableView and my tableViewCell has a isLiked variable and a likeButton. I would like to set my likeButton's tintColor accordingly to the state of isLiked. The way I have done it is that I retrieve a list of posts from Firebase that the user has liked, and then pass it into the tableView. If the postID tallies with the list, I would like to set the isLiked variable to true.
However, despite setting this logic in cellForRow, the isLiked variable in tableViewCell is not set accordingly. How can I get it set accordingly? My code as follows:
// in TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
for likedPosts in usersLikedPostsList {
if likedPosts == postID {
DispatchQueue.main.async {
cell.isLiked = true
}
} else {
cell.isLiked = false
}
}
return cell
}
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool?
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
print(isLiked)
if let _ = isLiked {
likeButton.tintColor = .red
} else {
likeButton.tintColor = .darkGray
}
}
}
From the print statement, isLiked variable is nil when the table is loaded. I have attempted with self.tableView.reloadData() but it does not set it accordingly as well.
Note that I would like to manipulate isLiked state in TableViewCell because I wanna add code to toggle its state within TableViewCell, so that users can like and unlike the post, and perform the updates to Firebase accordingly as well.

The cell awakeFromNib() method is executed only once, when the first time the cell is instanciated from storyboard or Nib file, so you need to update your cell every time this value isLiked change, try with his code
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool = false{
didSet{
likeButton.tintColor = .darkGray
if(isLiked)
{
likeButton.tintColor = .red
}
}
}
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Hope this helps you

let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
Try your above code in view did load and make postID as global variable.
and print to see if it returns anything or use .count to check it.
If it has count more than 1.Then try to use postID in method :cellForRowAt
Secondly, tableview reload will work only if numberOfRowsInSection have different value from last time.
Check whether this method is called or not when you reload table.

Related

Updating label in UITableViewCell with UIStepper in Swift

I'm a Swift beginner and I'm trying to make a simple app for ordering food. The user could add a new order by setting food name, price and serving. After adding an order, that order will be shown on the tableView as a FoodTableViewCell, and the user could change the serving with an UIStepper called stepper in each cell. Each order is a FoodItem stored in an array called foodList, and you can see all orders listed in a tableView in ShoppingListVC.
My problem is: When I press "+" or "-" button on stepper, my servingLabel doesn't change to corresponding value. I tried to use NotificationCenter to pass serving value to stepper, and store new value back to food.serving after stepperValueChanged with delegate pattern. However, there still seems to be some bugs. I've been kind of confused after browsing lots of solutions on the Internet. Any help is appreciated.
Update
I removed NotificationCenter and addTarget related methods as #Tarun Tyagi 's suggestion. Now my UIStepper value turns back to 1 whereas the servingLabels are showing different numbers of serving. Since NotificationCenter doesn't help, how can I connect the label and stepper value together? Is it recommended to implement another delegate?
Here are my codes(Updated on July 8):
FoodItem
class FoodItem: Equatable {
static func == (lhs: FoodItem, rhs: FoodItem) -> Bool {
return lhs === rhs
}
var name: String
var price: Int
var serving: Int
var foodID: String
init(name: String, price: Int, serving: Int) {
self.name = name
self.price = price
self.serving = serving
self.foodID = UUID().uuidString
}
}
ViewController
import UIKit
class ShoppingListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var foodList = [FoodItem]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
...
for i in 1...5 {
let testItem = FoodItem(name: "Food\(i)", price: Int.random(in: 60...100), serving: Int.random(in: 1...10))
self.foodList.append(testItem)
}
}
// MARK: - Table view data source
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "foodCell", for: indexPath) as! FoodTableViewCell
let food = foodList[indexPath.row]
cell.nameLabel.text = food.name
cell.priceLabel.text = "$\(String(food.price)) / serving"
cell.servingLabel.text = "\(String(food.serving)) serving"
cell.stepper.tag = indexPath.row
cell.delegate = self
return cell
}
}
// MARK: - FoodTableViewCellDelegate Method.
extension ShoppingListVC: FoodTableViewCellDelegate {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double) {
let indexPath = IndexPath(item: index, section: 0)
guard let cell = tableView.cellForRow(at: indexPath) as? FoodTableViewCell else { return }
let foodToBeUpdated = foodList[indexPath.row]
print("foodToBeUpdated.serving: \(foodToBeUpdated.serving)")
foodToBeUpdated.serving = Int(newValue)
print("Value changed in VC: \(newValue)")
cell.servingLabel.text = "\(String(format: "%.0f", newValue)) serving"
}
}
TableViewCell
import UIKit
protocol FoodTableViewCellDelegate: AnyObject {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double)
}
class FoodTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var servingLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
weak var delegate: FoodTableViewCellDelegate?
#IBAction func stepperValueChanged(_ sender: UIStepper) {
sender.minimumValue = 1
servingLabel.text = "\(String(format: "%.0f", sender.value)) serving"
// Pass the new value to ShoppingListVC and notify which cell to update using tag.
print("sender.value: \(sender.value)")
delegate?.stepper(stepper, at: stepper.tag, didChangeValueTo: sender.value)
}
override func awakeFromNib() {
super.awakeFromNib()
print(stepper.value)
}
}
Initially FoodTableViewCell is the ONLY target for UIStepper value changed (looking at #IBAction inside FoodTableViewCell).
When you dequeue a cell to display on screen, you call -
cell.stepper.addTarget(self, action: #selector(stepperValueChanged(_:)), for: .valueChanged)
which causes your ShoppingListVC instance to be added as an additional target every time a cellForRow call is executed.
Things to fix :
Remove all of your NotificationCenter related code from both classes.
Remove cell.stepper.addTarget() line as well.
This would give you a better idea of why it is happening this way. Update your question with these changes in case you still don't have what you want.
UPDATE
// Inside cellForRow
cell.stepper.value = food.serving
Cell Config:
protocol FoodTableViewCellDelegate: AnyObject {
func stepper(sender: FoodTableViewCell)
}
#IBAction func stepperButtonTapped(sender: UIStepper) {
delegate?.stepperButton(sender: self)
stepperLabel.text = "\(Int(countStepper.value))"
}
Controller Config:
cellForRow:
cell.countStepper.value = Double(foodList[indexPath.row].serving);
cell.stepperLabel.text = "\(Int(cell.countStepper.value))"
Delegate Method:
func stepperButton(sender: FoodTableViewCell) {
if let indexPath = tableView.indexPath(for: sender){
print(indexPath)
foodList[sender.tag].serving = Int(sender.countStepper.value)
}
}
Please check value stepper pod it will help you: Value stepper
Integrate value stepper pod and use below code for basic implementation.
import ValueStepper
let valueStepper: ValueStepper = {
let stepper = ValueStepper()
stepper.tintColor = .whiteColor()
stepper.minimumValue = 0
stepper.maximumValue = 1000
stepper.stepValue = 100
return stepper
}()
override func viewDidLoad() {
super.viewDidLoad()
valueStepper.addTarget(self, action: "valueChanged:", forControlEvents: .ValueChanged)
}
#IBAction func valueChanged1(sender: ValueStepper) {
// Use sender.value to do whatever you want
}
Its simplify custom stepper implantation.Take outlet of value stepper view in table tableview and use it.

UISwitch in tableview row

In the code below I'm populating my table with some data. The switches are off which they don't have to be. In the storyboard I defined it as On.
Cell:
var switchHandler: ((Bool)->Void)?
#IBAction func switchChanged(_ sender: UISwitch) {
self.switchHandler?(sender.isOn)
}
View controller:
var selectedCells = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as? SmsTableViewCell
cell?.PhonNumberLbl.text = data![indexPath.section].contacts[indexPath.row]?.phoneNumber
cell?.NameLbl.text = data![indexPath.section].contacts[indexPath.row]?.name
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
cell?.switchHandler = { (switchState) in
if switchState {
self.selectedCells.insert(indexPath)
} else {
self.selectedCells.remove(indexPath)
}
}
return cell!
}
Model:
typealias smsModelList = [SmsModel]
struct SmsModel:Codable {
var unitNo:Int?
var unitPlaque:String?
var billText:String?
var contacts:[ContactsModel?]
}
typealias contactlistmodel = [ContactsModel]
struct ContactsModel:Codable
{
var id :Int?
var selected :Bool?
var phoneNumber : String?
var name : String?
}
Does anybody see somthing wrong which turns off the switch?
First of all as you force unwrap the cell anyway do it in the dequeue line to avoid the unnecessary amount of question marks and use the API to return a non-optional cell
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: indexPath) as! SmsTableViewCell
To fix your issue update the selected property of the ContactsModel struct directly and forget the extra selectedCells array. Further declare – at least – selected as non-optional, practically there is no maybe state. And declare also all data source arrays (data / contacts) as non-optional, cellForRow is called only if there is an item at the particular indexPath by default.
struct ContactsModel : Codable {
...
var selected : Bool
...
}
...
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: IndexPath) as! SmsTableViewCell
let contact = data[indexPath.section].contacts[indexPath.row]
cell.PhonNumberLbl.text = contact.phoneNumber
cell.NameLbl.text = contact.name
cell.selectedTF.isOn = contact.selected
cell.switchHandler = { [unowned self] switchState in
// as the structs are value types you have to specify the full reference to the data source array
self.data[indexPath.section].contacts[indexPath.row].selected = switchState
}
Consider to use classes rather than structs in this case then you can shorten the closure
cell.switchHandler = { switchState in
contact.selected = switchState
}
You use both
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
so isOn property of the switch is controlled from 2 sides , so you have to decide which line that should be commnented , plus don't depend on storyboard prototype cell setup as because of cell reusing it' ll be changed , if you want to make them all on by default then change the var selectedCells to contain all possible indexPaths and comment the other one

Swift retain UISegmentedControl values in UITableViewCells

I'm creating a quiz app with custom cells that include a label of questions and then an answer coming from a UISegmentedControl.
The values of the segmentedcontrols get changed when scrolling and this leads to an inaccurate score. I understand that this is due to UITableView reusing cells.
My tableview's datasource in my main vc is simply the labels for all my questions coming from a plist file.
The code for my custom tableviewcell class is
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var question: String = "" {
didSet {
if (question != oldValue) {
questionLabel.text = question
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//Just for testing
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex);
}
}
where the View is stored in an .XIB file.
And the code for my main vc is
class ViewController: UIViewController, UITableViewDataSource {
let questionsTableIdentifier = "QuestionsTableIdentifier"
#IBOutlet var tableView:UITableView!
var questionsArray = [String]();
var questionsCellArray = [QuestionsTableViewCell]();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let path = Bundle.main.path(forResource:
"Questions", ofType: "plist")
questionsArray = NSArray(contentsOfFile: path!) as! [String]
tableView.register(QuestionsTableViewCell.self,
forCellReuseIdentifier: questionsTableIdentifier)
let xib = UINib(nibName: "QuestionsTableViewCell", bundle: nil)
tableView.register(xib,
forCellReuseIdentifier: questionsTableIdentifier)
tableView.rowHeight = 108;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: questionsTableIdentifier, for: indexPath)
as! QuestionsTableViewCell
let rowData = questionsArray[indexPath.row]
cell.question = rowData
return cell
}
#IBAction func calculate(_ sender: UIButton) {
var score = 0
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
score += cell.selection.selectedSegmentIndex
}
let msg = "Score is, \(score)"
print(msg)
}
#IBAction func reset(_ sender: UIButton) {
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
cell.selection.selectedSegmentIndex = 0;
}
}
}
What I'd like to do is just keep track of all 'selection' changes of the Questions cells in an array, and then use that array in cellForRowAt. I'm just confused as to how i can dynamically keep track of changes from a view in another class. I'm new to Swift and would like to solve this is a proper MVC fashion. Thanks
Instead of a simple string array as data source create a class holding the text and the selected index
class Question {
let text : String
var answerIndex : Int
init(text : String, answerIndex : Int = 0) {
self.text = text
self.answerIndex = answerIndex
}
}
Declare questionArray as
var questions = [Question]()
Populate the array in viewDidLoad with
let url = Bundle.main.url(forResource: "Questions", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let questionsArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String]
questions = questionsArray.map {Question(text: $0)}
In the custom cell add a callback and call it in the segmentChanged method passing the selected index, the property question is not needed, the label is updated in cellForRow of the controller
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var callback : ((Int) -> ())?
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex)
callback?(sender.selectedSegmentIndex)
}
}
In cellForRow add the callback and update the model in the closure
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: questionsTableIdentifier, for: indexPath) as! QuestionsTableViewCell
let question = questions[indexPath.row]
cell.questionLabel.text = question.text
cell.selection.selectedSegmentIndex = question.answerIndex
cell.callback = { index in
question.answerIndex = index
}
return cell
}
To reset the segmented controls in the cells set the property in the model to 0 and reload the table view
#IBAction func reset(_ sender: UIButton) {
questions.forEach { $0.answerIndex = 0 }
self.tableView.reloadData()
}
Now you could calculate the score directly from the model instead of the view.
Don't try to use cells to hold information. As the user scrolls through your table view, cells that scroll out of view will get recycled and their field settings will be lost. Also, newly dequeued cells will have the settings from the last time they were used.
You need to refactor your code to read/write information into a data model. Using an array of Structs as a data model is a reasonable way to go. (Or, as vadian suggests in his answer, and array of Class objects, so you get reference semantics.)
You have an IBAction segmentChanged() in your custom cell class. The next trick is to notify the view controller when the user changes the selection, and to update cells when you set them up in cellForRowAt.
I suggest defining a protocol QuestionsTableViewCellProtocol, and have the view controller conform to that protocol:
protocol QuestionsTableViewCellProtocol {
func userSelected(segmentIndex: Int, inCell cell: UITableViewCell)
}
}
Add a delegate property to your QuestionsTableViewCell class:
class QuestionsTableViewCell: UITableViewCell {
weak var delegate: QuestionsTableViewCellProtocol?
//The rest of your class goes here...
}
Update your cell's segmentChanged() method to invoke the delegate's userSelected(segmentIndex:inCell:) method.
In your view controller's cellForRowAt, set the cell's delegate to self.
func userSelected(segmentIndex: Int, inCellCell cell: UITableViewCell) {
let indexPath = tableView.indexPath(for: cell)
let row = indexPath.row
//The code below assumes that you have an array of structs, `dataModel`, that
//has a property selectedIndex that remembers which cell is selected.
//Adjust the code below to match your actual array that keeps track of your data.
dataModel[row].selectedIndex = segmentIndex
}
Then update cellforRowAt() to use the data model to set the segment index on the newly dequeued cell to the correct index.
Also update your calculate() function to look at the values in your dataModel to calculate the score, NOT the tableView.
That's a rough idea. I left some details out as "an exercise for the reader." See if you can figure out how to make that work.

UITableView: reloadRows(at:) takes two hits for table to be updated and scroll every time

I have a table view (controller: MetricsViewController) which gets updated from a CoreData database. I have used prototype cells (MetricsViewCell) which I have customized for my needs. It contains a segmented control, a UIView (metricsChart, which is used to display a chart - animatedCircle), and some UILabels.
MetricsViewCell:
class MetricsViewCell: UITableViewCell {
var delegate: SelectSegmentedControl?
var animatedCircle: AnimatedCircle?
#IBOutlet weak var percentageCorrect: UILabel!
#IBOutlet weak var totalPlay: UILabel!
#IBOutlet weak var metricsChart: UIView! {
didSet {
animatedCircle = AnimatedCircle(frame: metricsChart.bounds)
}
}
#IBOutlet weak var recommendationLabel: UILabel!
#IBOutlet weak var objectType: UISegmentedControl!
#IBAction func displayObjectType(_ sender: UISegmentedControl) {
delegate?.tapped(cell: self)
}
}
protocol SelectSegmentedControl {
func tapped(cell: MetricsViewCell)
}
MetricsViewController:
class MetricsViewController: FetchedResultsTableViewController, SelectSegmentedControl {
func tapped(cell: MetricsViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .none)
}
}
var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { didSet { updateUI() } }
private var fetchedResultsController: NSFetchedResultsController<Object>?
private func updateUI() {
if let context = container?.viewContext {
let request: NSFetchRequest<Object> = Object.fetchRequest()
request.sortDescriptors = []
fetchedResultsController = NSFetchedResultsController<Object>(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: "game.gameIndex",
cacheName: nil)
try? fetchedResultsController?.performFetch()
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Object Cell", for: indexPath)
if let object = fetchedResultsController?.object(at: indexPath) {
if let objectCell = cell as? MetricsViewCell {
objectCell.delegate = self
let request: NSFetchRequest<Object> = Object.fetchRequest()
...
...
}
}
}
return cell
}
When a user selects one of the segments in a certain section's segmented control, MetricsViewController should reload the data in that particular row. (There are two sections with one row each). Hence, I've defined a protocol in MetricsViewCell to inform inform my controller on user action.
Data is being updated using FetchedResultsTableViewController - which basically acts as a delegate between CoreData and TableView. Everything is fine with that, meaning I am getting the correct data into my TableView.
There are two issues:
I have to tap segmented control's segment twice to reload the data in the row where segmented control was tapped.
The table scrolls back up and then down every time a segment from segmented control is selected.
Help would be very much appreciated. I've depended on this community for a lot of issues I've faced during the development and am thankful already :)
For example, in Animal Recognition section, I have to hit "Intermediate" two times for its row to be reloaded (If you look closely, the first time I hit Intermediate, it gets selected for a fraction of second, then it goes back to "Basic" or whatever segment was selected first. Second time when I hit intermediate, it goes to Intermediate). Plus, the table scroll up and down, which I don't want.
Edit: Added more context around my usage of CoreData and persistent container.
Instead of using indexPathForRow(at: <#T##CGPoint#>) function to get the indexPath object of cell you can directly use indexPath(for: <#T##UITableViewCell#>) as you are receiving the cell object to func tapped(cell: MetricsViewCell) {} and try to update your data on the UI always in main thready as below.
func tapped(cell: MetricsViewCell) {
if let lIndexPath = table.indexPath(for: <#T##UITableViewCell#>){
DispatchQueue.main.async(execute: {
table.reloadRows(at: lIndexPath, with: .none)
})
}
}
Your UISegmentedControl are reusing [Default behaviour of UITableView].
To avoid that, keep dictionary for getting and storing values.
Another thing, try outlet connection as Action for UISegmentedControl in UIViewController itself, instead of your UITableViewCell
The below code will not reload your tableview when you tap UISegmentedControl . You can avoid, delegates call too.
Below codes are basic demo for UISegmentedControl. Do customise as per your need.
var segmentDict = [Int : Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0...29 // number of rows count
{
segmentDict[i] = 0 //DEFAULT SELECTED SEGMENTS
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SOTableViewCell
cell.mySegment.selectedSegmentIndex = segmentDict[indexPath.row]!
cell.selectionStyle = .none
return cell
}
#IBAction func mySegmentAcn(_ sender: UISegmentedControl) {
let cellPosition = sender.convert(CGPoint.zero, to: tblVw)
let indPath = tblVw.indexPathForRow(at: cellPosition)
segmentDict[(indPath?.row)!] = sender.selectedSegmentIndex
print("Sender.tag ", indPath)
}

send indexpath to Firebase (like button)

I have a table view and the cells are populated with data from Firebase. In each cell there is a like button, when I like a button in a particular cell, it captures the ID of that cell and creates a node in Firebase to let me know the button was clicked (liked). Before the button is clicked, it is white and after it is clicked it turns red. Then if it is clicked again (unliked) it turns white.
#IBAction func LikeClicked(_ sender: UIButton) -> Void {
let LikedRef = FIRDatabase.database().reference().child("Likes").child((self.loggedInUser?.uid)!)
let indexPath = self.selectedIndex
let post = self.posts![(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
let cell = TableView.cellForRow(at: indexPath!) as! ProfileTableViewCell
if cell.Like.currentImage == #imageLiteral(resourceName: "icons8-Hearts Filled-50 (2)"){
cell.Like.setImage(#imageLiteral(resourceName: "icons8-Heart-50"), for: .normal)
// cell.RedLike.isHidden = true
FIRDatabase.database().reference().child("Likes").child((self.loggedInUser?.uid)!).child(self.key!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}else{
}})
} else{
LikedRef.observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
var LikeStatus = postsDictionary[self.key!] as? String ?? ""
if self.key == LikeStatus
{
// cell.Like.isHidden = true
cell.Like.setImage(#imageLiteral(resourceName: "icons8-Hearts Filled-50 (2)"), for: .normal)
}
}})
LikedRef.updateChildValues([self.key!: self.key!])
}
}
cell.Like.addTarget(self, action: #selector(LikeClicked), for: UIControlEvents.touchUpInside)
cell.Like.tag = indexPath.row
print(indexPath.row)
cell.Like.isUserInteractionEnabled = true
My problem is when I like one button on a specific cell, all the like buttons in every cell turns red. but I only want the cell I click to turn red and when I leave the app and come back all the buttons are back to being white. I want whatever button the logged in user likes to remain red regardless of if the user exits the app or not.
Okay so I spent an hour or so to just give you an idea how you can do what you need to do. Liking and unliking from your custom UITableViewCell. I've explained in each line of code I made the details. I hope this helps you out. Let me know if you have questions. Remember this is just one of the many ways you can do your task.
MyViewController.swift
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MyCustomCellDelegate {
// This is the array of keys that we
var likedDataKeys = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Load here the 'Likes' stuff and store its in a datasource for reference or store as well its keys.
// If data is liked, store to likedDayaKeys the key of your data.
FirebaseCall {
if liked {
self.likedDataKeys.append(keyOfYourData)
}
}
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ....
// Set the image.
let dataKey = yourDatasource[indexPath.row] // get the key or whatever data you need
// Set the delegate and key
cell.delegate = self
cell.dataKey = dataKey
if likedDataKeys.contains(dataKey) {
cell.image = redImageLike
} else {
cell.image = whiteNormalLikeImage
}
return cell
}
// MARK: - MyCustomCellDelegate
func myCustomCell(userDidTapLikeWithDataKey dataKey: String) {
// So now we can get the dataKey of the cell that is being liked or unliked.
// Check from the self.likedDataKeys if the tapped cell is liked or not.
if self.likedDataKeys.contains(dataKey) {
// If it is there, then we should call the unlike Firebase.
// Also remove it from the self.likedIndexPath and reload the tableView to update the image.
let index = self.likedDataKeys.index(of: dataKey)
self.likedDataKeys.remove(at: index)
// Call now the unlike Firebase.
} else {
// If it is not there, then we should call the like Firebase.
// Also store it to the self.likedIndexPAth
}
}
}
MyCustomCell.swift
protocol MyCustomCellDelegate: NSObjectProtocol {
// This is the delegate that will help us send the dataKey reference to the viewController
// Whenever the user taps on the like button in the cell.
func myCustomCell(userDidTapLikeWithDataKey dataKey: String)
}
class MyCustomCell: UITableViewCell {
// This will be called in the viewController, pass here the self of the viewController
weak var delegate: MyCustomCellDelegate?
// Make sure to pass here the key from the cellForRow of the viewController's tableView delegate.
var dataKey = ""
#IBAction func LikeClicked(_ sender: UIButton) -> Void {
// Call the delegate to inform the viewController
self.delegate?.myCustomCell(userDidTapLikeWithDataKey: self.dataKey)
}
}

Resources