Search code examples
iosswiftiphoneuitableviewnsuserdefaults

How to save UILabel from custom cell so that every time I open the app it displays the latest change?


I am a beginner in Swift. I've exhausted all my trial and errors and need help!!

I am creating a scoreboard project using a UITableView with a Custom Cell that holds a UILabel and a UIButton. After a button press the UILabel increments by one to simulate a point for the player. I am having trouble saving the point in UILabel so that every time I open the app the point for that player remains. I've tried using UserDefaults, structs, and delegates but have't had any luck...I could be doing it wrong. I just need to know what the proper approach is for this.

Note: I am able to save the player name successfully from the UIAlertController so that when I open the app the names are still there unless I delete them, but haven't had any luck saving the points for each name itself, they still remain "0".

It should look like this when I close then open the app, but it only does this when the app is opened:

Scoreboard UITableView - Screenshot

Here's the ViewController code:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var items = [String]()

    @IBOutlet weak var listTableView: UITableView!
    @IBAction func addItem(_ sender: AnyObject) {
        alert()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        listTableView.dataSource = self
        self.items = UserDefaults.standard.stringArray(forKey:"items")  ?? [String]()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! PointsCell

        cell.textLabel?.text = items[indexPath.row]

        return cell
    }


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func saveData() {
        UserDefaults.standard.set(items, forKey: "items")
    }

    func alert(){
        let alert = UIAlertController(title: "Add Player", message: "", preferredStyle: .alert)
        alert.addTextField{
            (textfield) in
            textfield.placeholder = " Enter Player Name "

        }
        let add = UIAlertAction(title: "Add", style: .default)
            {

            (action) in guard let textfield = alert.textFields?.first else {return}

            if let newText = textfield.text
            {
                self.items.append(newText)
                self.saveData()
                let indexPath = IndexPath(row: self.items.count - 1, section: 0)
                self.listTableView.insertRows(at: [indexPath], with: .automatic)
            }
        }

        let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
            (alert) in

        }

        alert.addAction(add)
        alert.addAction(cancel)

        present(alert, animated: true, completion: nil)

    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        items.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
        saveData()

    }

}

Here is my custom cell code called PointsCell:

import UIKit

class PointsCell: UITableViewCell {

    var winScore = 0
    @IBOutlet weak var scoreUILabel: UILabel!

   @IBAction func pointButtonPressed(_ sender: Any) {
        winScore += 1
        scoreUILabel.text = "\(winScore)"

    }

}

Solution

  • The points still remains 0 because you are note saving them, firstly you need points for each player so you need to combine player name and score into object.

    class Player:NSObject, Codable{
        let name: String
        var score : Int
        init(name: String, score: Int) {
            self.name = name
            self.score = score
        }
        override var description: String{
            return "Name :" + self.name + " Score :" + String(self.score )
        }
    }
    

    Swift 4 introduced the Codable protocol, by adopting Codable on your own types enables you to serialize them to and from any of the built-in data formats.

    Now you can easily access a player with name and score.

    var players = [Player]()
    

    To get stored value from UserDefaults

    override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            if let items: Data = UserDefaults.standard.object(forKey: "items") as? Data{
                self.players = try! PropertyListDecoder().decode([Player].self, from: items)
                self.listTableView.reloadData()
            }
        }
    

    When you adding new player to your list create new instance of Player and add it to list.

    func alert(){
        let alert = UIAlertController(title: "Add Player", message: "", preferredStyle: .alert)
        alert.addTextField{
            (textfield) in
            textfield.placeholder = " Enter Player Name "
    
        }
        let add = UIAlertAction(title: "Add", style: .default)
        {
            (action) in guard let textfield = alert.textFields?.first else {return}
            if let newText = textfield.text
            {
                let player = Player(name: newText, score: 0)
                self.players.append(player)
                let indexPath = IndexPath(row: self.players.count - 1, section: 0)
                self.listTableView.insertRows(at: [indexPath], with: .automatic)
            }
        }
    
        let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
            (alert) in
    
        }
        alert.addAction(add)
        alert.addAction(cancel)
        present(alert, animated: true, completion: nil)
    
    }
    

    Now update your UITableViewDataSource method as new list item.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! PointsCell
        cell.player = players[indexPath.row]
        return cell
    }
    

    now need to update saveData method to save new list into UserDefaults, you'll call this method whenever you want to save your list.

    func saveData() {
        UserDefaults.standard.set(try! PropertyListEncoder().encode(self.players), forKey: "items")
    }
    

    PointsCell class also need to be updated as new object type:

    class PointsCell: UITableViewCell {
        @IBOutlet weak var scoreUILabel: UILabel!
        @IBOutlet weak var nameUILabel: UILabel!
    
        var player: Player? {
            didSet{
                if let name = player?.name {
                    self.nameUILabel?.text = name
                }
                if let score = player?.score {
                    self.scoreUILabel?.text = String(score)
                }
            }
        }
    
        @IBAction func pointbuttonPressed(_ sender: Any) {
            if self.player != nil{
                let score = self.player?.score ?? 0
                self.player?.score = score + 1
                scoreUILabel.text = String(self.player?.score ?? 0)
            }
        }
    }