Swift将UISegmentedControl值保留在UITableViewCells中

时间:2018-07-28 19:27:35

标签: ios swift uitableview model-view-controller

我正在创建一个包含自定义单元格的测验应用程序,其中包括一个问题标签,然后一个来自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方式。谢谢

2 个答案:

答案 0 :(得分:2)

不要尝试使用单元格来保存信息。当用户滚动查看表格视图时,滚动出视图的单元格将被回收,其字段设置将丢失。此外,新出队的单元将具有自上次使用以来的设置。

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

自定义单元格类中有一个IBAction <? intents ?>。下一个技巧是在用户更改选择时通知视图控制器,并在cellForRowAt中设置它们时更新它们。

我建议定义一个协议segmentChanged(),并让视图控制器遵守该协议:

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()方法。

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

userSelected(segmentIndex:inCell:)

然后更新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()函数以查看dataModel中的值以计算得分,而不是tableView。

这是一个粗略的主意。我把一些细节留给了“读者练习”。看看您是否能弄清楚该如何做。

答案 1 :(得分: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得分。