【问题标题】:Swift retain UISegmentedControl values in UITableViewCellsSwift 在 UITableViewCells 中保留 UISegmentedControl 值
【发布时间】:2019-01-05 13:23:12
【问题描述】:

我正在创建一个带有自定义单元格的测验应用程序,其中包含问题标签和来自 UISegmentedControl 的答案。

segmentedcontrols 的值在滚动时会发生变化,这会导致分数不准确。我知道这是由于 UITableView 重用了单元格。

我的主 vc 中的 tableview 数据源只是来自 plist 文件的所有问题的标签。

我的自定义 tableviewcell 类的代码是

class QuestionsTableViewCell: UITableViewCell {

    @IBOutlet weak var questionLabel: UILabel!
    @IBOutlet weak var selection: UISegmentedControl!

    var question: String = "" {
        didSet {
            if (question != oldValue) {
                questionLabel.text = question
            }
        }
    }

    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
    }

    //Just for testing
    @IBAction func segmentChanged(_ sender: UISegmentedControl) {
        print("value is ", sender.selectedSegmentIndex);
    }
}

视图存储在 .XIB 文件中的位置。

我的主要 vc 的代码是

class ViewController: UIViewController, UITableViewDataSource {

    let questionsTableIdentifier = "QuestionsTableIdentifier"
    @IBOutlet var tableView:UITableView!

    var questionsArray = [String]();

    var questionsCellArray = [QuestionsTableViewCell]();

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

        let path = Bundle.main.path(forResource:
            "Questions", ofType: "plist")
        questionsArray = NSArray(contentsOfFile: path!) as! [String]

        tableView.register(QuestionsTableViewCell.self,
                           forCellReuseIdentifier: questionsTableIdentifier)
        let xib = UINib(nibName: "QuestionsTableViewCell", bundle: nil)
        tableView.register(xib,
                           forCellReuseIdentifier: questionsTableIdentifier)
        tableView.rowHeight = 108;
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

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

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

        let rowData = questionsArray[indexPath.row]
        cell.question = rowData
        return cell
    }

    @IBAction func calculate(_ sender: UIButton) {

        var score = 0

        for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
            score += cell.selection.selectedSegmentIndex
        }

        let msg = "Score is, \(score)"

        print(msg)
    }

    @IBAction func reset(_ sender: UIButton) {
        for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
            cell.selection.selectedSegmentIndex = 0;
        }

    }
}

我想做的只是跟踪数组中问题单元格的所有“选择”更改,然后在 cellForRowAt 中使用该数组。我只是对如何动态跟踪另一个类中的视图的更改感到困惑。我是 Swift 的新手,想解决这个问题,这是一种合适的 MVC 方式。谢谢

【问题讨论】:

  • TableView 单元格被重用为滚动以使其平滑,因此您无法在其中存储信息。您需要在后台将分段控件的状态存储在数据模型中,然后根据数据在cellForRow 方法中设置它的状态。
  • @Chris“让它顺利”这不是原因。实际上,单元格重用是平滑滚动的问题
  • @matt 是为了响应吗?还是只是为了减少内存开销?
  • 用于内存开销。
  • 这很有道理 - 谢谢你们! :) 我知道重用对我的代码意味着什么,但现在我知道为什么要使用它了。

标签: ios swift uitableview model-view-controller


【解决方案1】:

不要尝试使用单元格来保存信息。当用户滚动您的表格视图时,滚动到视图之外的单元格将被回收,并且它们的字段设置将丢失。此外,新出列的单元格将具有上次使用时的设置。

您需要重构代码以将信息读/写到数据模型中。使用结构数组作为数据模型是一种合理的方法。 (或者,正如 vadian 在他的回答中建议的那样,以及 Class 对象数组,因此您可以获得引用语义。)

您的自定义单元格中有一个 IBAction segmentChanged()。下一个技巧是在用户更改选择时通知视图控制器,并在您在 cellForRowAt 中设置单元格时更新单元格。

我建议定义一个协议QuestionsTableViewCellProtocol,并让视图控制器符合该协议:

protocol QuestionsTableViewCellProtocol {
    func userSelected(segmentIndex: Int, inCell cell: UITableViewCell)
    }
}

将委托属性添加到您的 QuestionsTableViewCell 类:

class QuestionsTableViewCell: UITableViewCell {
   weak var delegate: QuestionsTableViewCellProtocol?

   //The rest of your class goes here...
}

更新单元格的 segmentChanged() 方法以调用委托的 userSelected(segmentIndex:inCell:) 方法。

在视图控制器的 cellForRowAt 中,将单元格的委托设置为 self。

func userSelected(segmentIndex: Int, inCellCell cell: UITableViewCell) {
   let indexPath = tableView.indexPath(for: cell)
   let row = indexPath.row
   //The code below assumes that you have an array of structs, `dataModel`, that
   //has a property selectedIndex that remembers which cell is selected.
   //Adjust the code below to match your actual array that keeps track of your data.
   dataModel[row].selectedIndex = segmentIndex
}

然后更新cellforRowAt() 以使用数据模型将新出列的单元格上的段索引设置为正确的索引。

同时更新您的 calculate() 函数以查看 dataModel 中的值来计算分数,而不是 tableView。

这是一个粗略的想法。我留下了一些细节作为“读者练习”。看看你能不能弄清楚如何让它发挥作用。

【讨论】:

    【解决方案2】:

    创建一个来保存文本和选定的索引,而不是一个简单的字符串数组作为数据源

    class Question {
        let text : String
        var answerIndex : Int
    
        init(text : String, answerIndex : Int = 0) {
            self.text = text
            self.answerIndex = answerIndex
        }
    }
    

    声明questionArray

    var questions = [Question]()
    

    填充viewDidLoad中的数组
    let url = Bundle.main.url(forResource: "Questions", withExtension: "plist")!
    let data = try! Data(contentsOf: url)
    let questionsArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String]
    questions = questionsArray.map {Question(text: $0)}
    

    在自定义单元格中添加一个回调并在segmentChanged方法中调用它并传递所选索引,不需要question属性,标签在控制器的cellForRow中更新

    class QuestionsTableViewCell: UITableViewCell {
    
        @IBOutlet weak var questionLabel: UILabel!
        @IBOutlet weak var selection: UISegmentedControl!
    
        var callback : ((Int) -> ())?
    
        @IBAction func segmentChanged(_ sender: UISegmentedControl) {
            print("value is ", sender.selectedSegmentIndex)
            callback?(sender.selectedSegmentIndex)
        }
    }
    

    cellForRow 中添加回调并更新闭包中的模型

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: questionsTableIdentifier, for: indexPath) as! QuestionsTableViewCell
    
        let question = questions[indexPath.row]
        cell.questionLabel.text = question.text
        cell.selection.selectedSegmentIndex = question.answerIndex
        cell.callback = { index in
           question.answerIndex = index
        }
        return cell
    }
    

    要重置单元格中的分段控件,请将模型中的属性设置为 0 并重新加载表格视图

    @IBAction func reset(_ sender: UIButton) {
        questions.forEach { $0.answerIndex = 0 }
        self.tableView.reloadData()
    }
    

    现在您可以直接从模型而不是视图中calculate 得分。

    【讨论】:

    • 所有单元格的选择在滚动回时默认为 0。我确实在你的 cellForRowAt 函数中得到了“无法分配给属性:'问题'是一个'让'常量”。我只是将 'let question' 更改为 'var question' 以使其编译。但我认为这个问题与此无关。有什么想法吗?
    • 在这种情况下,最好使用类而不是结构来获取引用语义。我更新了答案。这也解决了Cannot assign 错误。
    • 非常感谢瓦迪安!我接受了这个答案,因为它完美运行并且清晰简洁。您能否进一步解释为什么在结构上使用类使其在获取引用语义方面起作用?
    • 一个结构有值语义。 let question = questions[indexPath.row] 复制数组中的项目。您将更改此项目,但不会更改数据源数组中的项目。引用语义保持对数据源项的引用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-01
    • 2018-05-13
    • 1970-01-01
    相关资源
    最近更新 更多