Search code examples
iosswiftuitableviewnsuserdefaultsuiswitch

Switch in a tableview with UserDefaults, several is activated by itself


I have a problem, I explain.

I want to activate a Switch and it is possible to open the application in a tableView.

For this I save the label in an NSUserDefault and if a the opening of the application the label is in the NSUserDefailt it activates the switch.

The problem is that on the following pictures I just switch on switch A and B but when I scrool other switch activates as q, s, c but never the same.

Do you have an idea to suggest me? I but my code below with the console.

Screen 1

Screen 2

ViewController :

@IBOutlet weak var tableView_t: UITableView!

let station = ["A","B","C","D","E","F","G","H","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","&","é","("]

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

    cell?.configCell(station[indexPath.item])

    return cell!
}

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

TableViewCell :

@IBOutlet weak var switch_t: UISwitch!
@IBOutlet weak var label_t: UILabel!

func configCell(_ labels : String)
{
    label_t.text = labels

    if let sav = UserDefaults.standard.value(forKey: "switch") as? [String]
    {
        print(sav)
        for switchs in sav
        {
            print(switchs)
            if switchs == labels
            {
                switch_t.setOn(true, animated: false)
                print("On")
            }
        }
     }
}

@IBAction func actionSwitch(_ sender: Any)
{
    var sav : [String] = []

    if let saving = UserDefaults.standard.value(forKey: "switch") as? [String]
    {
        sav = saving
    }

    if switch_t.isOn
    {
        sav.append(label_t.text!)
        UserDefaults.standard.set(sav, forKey: "switch")
        print(sav)
    }
    else
    {
        var number = 0

        for s in sav
        {
            if s == label_t.text
            {
                sav.remove(at: number)
                UserDefaults.standard.set(sav, forKey: "switch")
            }
            number += 1
            print(sav)
        }
    }

}  

Console :

["A", "B"] A On B ["A", "B"] A B On ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B ["A", "B"] A B


Solution

  • Cells get reused. You must always set a cell's properties for every condition to fully setup the cell for each index path. Your code only deals with turning a cell's switch on, but not off.

    There are two solutions.

    1. Update your configCell method to turn the switch off under any conditions it should not be turned on.
    2. Override the cell's prepareForReuse method. In this method you should reset all state of the cell. This would include setting the switch to off and clearing the label's text.

    On a side note, your code has several other minor issues that should be cleaned up.

    1. Don't use key-value coding with UserDefaults unless you have a clearly understood need to do so. Use the proper methods to save and read values from UserDefaults.
    2. In your cellForRowAt you should force-cast the cell to the desired type. This makes the rest of the code in that method easier to write and you want the app to crash quickly if the cast is wrong because it means you have a programming error that needs to be fixed.
    3. Minor - the parameter to your configCell method is called labels but it only repents a single label. Giving it a plural name makes it confusing.
    4. Your logic for checking if a cell's switch should be on or not is far more complicated than it needs to be. Use the contains method of Array.
    5. Your code for updating UserDefaults is all more complicated than it needs to be.
    6. You are referencing the item property of IndexPath. This is meant to be used with UICollectionView. For UITableView, use row.

    Below is how I would write your code after fixing all of the above issues.

    Table controller:

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

    Cell code:

    func configCell(_ label: String) {
        label_t.text = label
    
        if let sav = UserDefaults.standard.array(forKey: "switch") as? [String] {
            switch_t.isOn = sav.contains(label)
        } else {
            switch_t.isOn = false
        }
    }
    
    @IBAction func actionSwitch(_ sender: UISlider) {
        var labels = (UserDefaults.standard.array(forKey: "switch") as? [String]) ?? []
    
        if let text = label_t.text {
            if switch_t.isOn {
                labels.append(text)
            } else {
                if let index = labels.indexOf(text) {
                    labels.remove(at: index)
                }
            }
            UserDefaults.standard.set(labels, forKey: "switch")
        }
    }