滚动时 TableViewCell 中的 Swift 3 UISwitch 丢失状态

Posted

技术标签:

【中文标题】滚动时 TableViewCell 中的 Swift 3 UISwitch 丢失状态【英文标题】:Swift 3 UISwitch in TableViewCell loses State when scrolling 【发布时间】:2017-06-05 12:47:37 【问题描述】:

这很奇怪:我刚刚建立了一个新的 Single-View ios 项目并将一个 TableView 放入 Main.storyboard。在这个 TableView 中,我放置了一个 TableViewCell,在这个 Cell 中,我放置了一个 UILabel 和一个 UISwitch。 对于这个 TableViewCell,我创建了一个 CocoaTouchClass MyTableViewCell 并在 Interfacebuilder 中为 TableViewCell 的类设置它。 我将 UILabel 和 UISwitch 的 Outlets 连接到 MyTableViewCell,以及开关的操作。 我还连接了 TableView 的数据源并委托给 ViewController。

所以,我认为,设置表格的基本内容。

我的 ViewController 看起来像这样:

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

所以,我用一些数据行填充表格,并每隔一秒将 UISwitch 切换为 on。

MyTableViewCell-Class 也没什么特别的:

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)")

好的,启动 iOS 模拟器,我看到了预期的表格。 40 条 Table-Lines 无法在一个屏幕上显示,因此 TableView 可以自行滚动。

现在:当我更改一个 UISwitch 的状态并拖动 TableView 使更改后的 UISwitch 消失,然后拖动 TableView 使更改后的 UISwitch 再次可见时,它会更改回其初始状态。 Switch 事件按原样触发。

那么,我做错了什么?我错过了什么吗?

我录制了一个 4 秒的屏幕录像来演示发生了什么: http://b-bereich.de/download/swiftSwitch.mov

【问题讨论】:

@them Sebastian de Vries 表格视图单元格在您向下滚动时被重复使用。您看到的是打开开关后再次可重复使用的单元格。 【参考方案1】:

TableViewCell 被重复使用

这意味着您需要跟踪用于填充单元格内容的数据。如果单元格内容发生变化 - 例如当您点击单元格中的 Switch 时 - 您需要更新您的 数据源。当您滚动时,该行再次显示,您的数据将知道如何设置 Switch 的状态。

这是一个简单的例子:

//
//  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
    



【讨论】:

太好了,这个回调就是解决问题的方法。谢谢!【参考方案2】:

我认为每次翻转开关时都需要让 IBAction 更改 Bool。目前,该开关会更改 UI,但不会更改底层 Bool,因此当单元格回滚到屏幕上时,cellForRowAt 方法会使用当前保存的值。

【讨论】:

【参考方案3】:

在您的自定义单元类中添加函数 prepareForReuse() 并将开关状态默认设置为关闭。 -:

 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
    

【讨论】:

以上是关于滚动时 TableViewCell 中的 Swift 3 UISwitch 丢失状态的主要内容,如果未能解决你的问题,请参考以下文章

在 tableviewcell 内滚动时防止 tableview 滚动

滚动时对 TableViewCell 的模糊效果

Tableviewcell 内容在滚动时四处移动

当用户滚动到最后一个 tableViewCell 时执行操作

当单元格滚动出视图时,TableViewCell 的 textLabel 值返回 1

tableViewCell 的高度和宽度在快速滚动时发生变化