I have a table view with a "like" button. Users can tap to like the content in each cell. I want to use UserDefaults to persist the button status so that it gets saved if a user closes the app. I'm a new developer so I was hoping for guidance on getting started.
Here is the code for the table view cell: import UIKit
class QuotesTableViewCell: UITableViewCell {
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var quoteLabel: UILabel!
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
}
@IBAction func likeTapped(_ sender: UIButton) {
if likeButton.tag == 0{
likeButton.setImage(UIImage(named: "GreenHeart"), for: .normal)
likeButton.tag = 1
} else{
likeButton.setImage(UIImage(named: "blankHeart"), for: .normal)
likeButton.tag = 0
}
}
}
And here is the code for the view controller:
import UIKit
class QuotesMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var quotesTableView: UITableView!
let quoteList = ["Quote1", "Quote2", "Quote3"]
override func viewDidLoad() {
super.viewDidLoad()
quotesTableView.delegate = self
quotesTableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return quoteList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = quotesTableView.dequeueReusableCell(withIdentifier: "cell") as! QuotesTableViewCell
cell.quoteLabel.text = quoteList[indexPath.row] as! String
return cell
}
}
The first principle in a TableView is that all data used to configure cells are stored in the dataSource. NEVER use cell as a dataStorage, it is only for data display. If you use as data storage, when you dequeue, you get wrong values in cell.
- In your case, your dataSource could be an array, defined in QuotesMainViewController:
struct Quote {
var label: String
var like: Bool = false // a priori, false
}
var quoteList: [Quote]?
- And in viewDidLoad:
quoteList = [Quote(label: "Quote1"), Quote(label: "Quote2"), Quote(label: "Quote3")]
- You use it in cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = quotesTableView.dequeueReusableCell(withIdentifier: "cell") as! QuotesTableViewCell
cell.quoteLabel.text = quoteList[indexPath.row].label
cell.likeButton.tag = indexPath.row // Use tag to reference the cell, not to set true / false
cell.likeButton.setImage(UIImage(named: quoteList[indexPath.row].like ? "GreenHeart" : "blankHeart"), for: .normal)
return cell
}
- Put the IBAction in QuotesMainViewController and use the dataSource:
@IBAction func likeTapped(_ sender: UIButton) {
quoteList[sender.tag].like.toggle() // update the dataSource ; sender.tag gives the row in the array
if quoteList[sender.tag].like {
sender.setImage(UIImage(named: "GreenHeart"), for: .normal) // You can change here or ask for a reloadData()
} else {
sender.setImage(UIImage(named: "blankHeart"), for: .normal)
}
}
- Then, when needed, save quoteList in UserDefaults, or just extract the like values:
let quoteLikes :[Bool] = quoteList.map() { $0.like }
But it is better to save the full array
- save defaults when appropriate (may be at the end of IBAction):
@IBAction func likeTapped(_ sender: UIButton) {
quoteList[sender.tag].like.toggle() // update the dataSource ; sender.tag gives the row in the array
if quoteList[sender.tag].like {
sender.setImage(UIImage(named: "GreenHeart"), for: .normal) // You can change here or ask for a reloadData()
} else {
sender.setImage(UIImage(named: "blankHeart"), for: .normal)
}
let defaults = UserDefaults.standard
defaults.set(quoteList, forKey: "QuoteListKey") // or defaults.set(quoteLikes, forKey: "LikeKey")
}
- read them when needed, in viewDidLoad, by changing
quoteList = [Quote(label: "Quote1"), Quote(label: "Quote2"), Quote(label: "Quote3")]
with
let defaults = UserDefaults.standard
quoteList = UserDefaults.standard.string(forKey: "QuoteListKey") ?? [Quote(label: "Quote1"), Quote(label: "Quote2"), Quote(label: "Quote3")]