I have a simple app similar to to-do list. I used TableViewController
and I was able to set the checkmark accessory type for each row, also found a way to save the rows data so that when the app relaunches the list of items are shown again.
I want to save the checkmark state for all the rows. I tried other queries in Stack Overflow most of them are outdated.
here's my code to the app
class ViewController: UITableViewController {
var shoppingList = [ShoppingList]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Shopping List"
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))
let clearList = UIBarButtonItem(title: "Clear List", style: .plain, target: self, action: #selector(clearList))
toolbarItems = [clearList]
navigationController?.isToolbarHidden = false
load()
tableView.tableFooterView = UIView() // clears the unused seperator lines
}
@objc func clearList() {
shoppingList.removeAll(keepingCapacity: true)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
shoppingList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Word", for: indexPath)
cell.textLabel?.text = shoppingList[indexPath.row].itemName
cell.accessoryType = shoppingList[indexPath.row].checkmarkState ? .checkmark : .none
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
shoppingList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
shoppingList[indexPath.row].checkmarkState = false
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
shoppingList[indexPath.row].checkmarkState = true
}
save()
}
@objc func addTapped() {
let ac = UIAlertController(title: "New Shopping Item", message: nil, preferredStyle: .alert)
ac.addTextField()
let addAction = UIAlertAction(title: "Add", style: .default) {
[weak self, weak ac] _ in
guard let item = ac?.textFields?[0].text else { return }
self?.addItem(item)
}
ac.addAction(addAction)
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(ac, animated: true)
}
@objc func shareTapped() {
}
func addItem(_ item: String) {
let itemCapitalised = item.capitalized
shoppingList.insert(ShoppingList(itemName: itemCapitalised), at: 0)
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
save()
}
func showErrorMessage(errorTitle: String, errorMessage: String) {
let ac = UIAlertController(title: errorTitle, message: errorMessage, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Ok", style: .default))
present(ac, animated: true)
}
func save() {
let jsonEncoder = JSONEncoder()
if let savedData = try? jsonEncoder.encode(shoppingList) {
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "shoppingList")
} else {
print("Failed to save people.")
}
}
func load() {
let defaults = UserDefaults.standard
if let savedList = defaults.object(forKey: "shoppingList") as? Data {
let jsonDecoder = JSONDecoder()
do {
shoppingList = try jsonDecoder.decode([ShoppingList].self, from: savedList)
} catch {
print("Failed to load List")
}
}
}
}
CodePudding user response:
As you already have stored checkmarkState
in your model, simply add cell.accessoryType = shoppingList[indexPath.row].checkmarkState ? .checkmark : .none
in tableView(_:cellForRowAt:)
to render the checkmark state.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Word", for: indexPath)
cell.textLabel?.text = shoppingList[indexPath.row].itemName
cell.accessoryType = shoppingList[indexPath.row].checkmarkState ? .checkmark : .none
return cell
}
Also, there is a better way to write your tableView(_:didSelectRowAt:)
: Just invert the checkmarkState
, then reload the row.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
shoppingList[indexPath.row].checkmarkState = !shoppingList[indexPath.row].checkmarkState
tableView.reloadRows(at: [indexPath], with: .automatic)
save()
}
Try to keep in mind the MVC philosophy. Never determine the state of your model by the UI view. Only determine the state of UI view by the model. When you receive user input, change the model directly, then re-render the view accordingly. Keep the model as single source of truth.