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