简体   繁体   中英

Swift 3 UISwitch in TableViewCell loses State when scrolling

That's weird: I just set up a new Single-View iOS Project and put a TableView into the Main.storyboard. In this TableView I put a TableViewCell and into this Cell, I put an UILabel and an UISwitch. For this TableViewCell I created a CocoaTouchClass MyTableViewCell and set this for the TableViewCell's Class in Interfacebuilder. I connected Outlets for the UILabel and UISwitch to MyTableViewCell, as well as an action for the switch. Also I connected the TableView's dataSource and delegate to the ViewController.

So, as I think, basic stuff for setting up a table.

My ViewController looks like this:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

var tableData = [[String: Bool]]()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    for index in 1...40 {
        self.tableData.append([String(index): index%2 == 0])
    }
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

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


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyTableViewCell
    let object = tableData[indexPath.row].first!

    cell.myLabel.text = object.key
    cell.mySwitch.setOn(object.value, animated: false)

    return cell
}

}

So, I populate the Table with some Rows of Data and switch every second UISwitch to on.

The MyTableViewCell-Class is nothing special as well:

class MyTableViewCell: UITableViewCell {
@IBOutlet weak var myLabel: UILabel!
@IBOutlet weak var mySwitch: UISwitch!

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 switched(_ sender: UISwitch) {
    print("Switched: \(sender.isOn)")
}

}

Ok, fire up the iOS-Simulator and I see the Table as expected. 40 Table-Lines don't fit on one screen, so the TableView makes itself scrollable.

Now: when I change the state of one UISwitch, and drag the TableView so the changed UISwitch gets out of view and then drag the TableView so the changed UISwitch gets visible again, it is changed back to its initial state. The Switch-event gets fired like it should.

So, what am I doing wrong? Am I missing something?

I recorded a 4-seconds-Screencast to demonstrate, what's going on: http://b-bereich.de/download/swiftSwitch.mov

TableViewCells are reused .

That means you need to keep track of the data you are using to fill the content of the cells. If the cell content changes - such as when you tap a Switch in the cell - you need to update your datasource . When you scroll, and that row is displayed again, your data will know how to set the state of the Switch.

Here is a simple example:

//
//  TableWithSwitchTableViewController.swift
//  SWTemp2
//
//  Created by Don Mag on 6/5/17.
//  Copyright © 2017 DonMag. All rights reserved.
//

import UIKit

class MyTableViewCell: UITableViewCell {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var mySwitch: UISwitch!

    var switchTapAction : ((Bool)->Void)?

    @IBAction func switched(_ sender: UISwitch) {
        print("Switched: \(sender.isOn)")

        // send the Switch state in a "call back" to the view controller
        switchTapAction?(sender.isOn)
    }
}

// simple data object
class MyObject: NSObject {
    var theTitle = ""
    var theSwitchState = false

    init(_ title: String) {
        theTitle = title
    }
}

class TableWithSwitchTableViewController: UITableViewController {

    // array of MyObjects
    var myData = [MyObject]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // just to make it a little easier to see the rows scroll
        tableView.rowHeight = 60

        // create 40 data objects for the table
        for i in 1...40 {
            let d = MyObject("Data Item: \(i)")
            myData.append(d)
        }

        tableView.reloadData()

    }

    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

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

        // Configure the cell...
        let d = myData[indexPath.row]
        cell.myLabel.text = d.theTitle
        cell.mySwitch.isOn = d.theSwitchState

        // set a "Callback Closure" in the cell
        cell.switchTapAction = {
            (isOn) in
            // update our Data Array to the new state of the switch in the cell
            self.myData[indexPath.row].theSwitchState = isOn
        }

        return cell
    }


}

I think you need to have your IBAction change the Bool each time you flip the switch. Currently the switch changes the UI but not the underlying Bool so the cellForRowAt method uses the currently saved value when the cell scrolls back on screen.

Add function prepareForReuse() in your custom cell class and set switch state to off by default. -:

 class MyTableViewCell: UITableViewCell {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var mySwitch: UISwitch!

    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 switched(_ sender: UISwitch) {
        print("Switched: \(sender.isOn)")
    }

    override func prepareForReuse(){
    // SET SWITCH STATE OFF HERE
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM