My pickers are behaving erratically in my table. The data for the number of picker rows to show seems to be the problem.
I'm using a table to collect user settings and one section contains 5 rows, each with an expanding custom cell containing a picker view. So selecting one row expands and shows the following row with a picker to select a desired unit, °F or °C for instance in the temperature units picker and psi or atm in the pressure units picker
This all works perfectly for the first picker selected. However the next picker selected will only show as many rows as the previously selected picker. So if the first picker selected has two rows, the second one selected will show only the first two entries in its array of string values even though those two entries are from the correct array containing more values. Closing that row and reselecting it will show everything properly. If you select any picker with less values available in the array than the previously selected had, the app crashes because of an index out of range.
I'm at wit's end and feel like I've tried everything. What am I missing?
In my CustomCell:
class CustomCell: UITableViewCell, UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var unitPicker: UIPickerView! // This is the troublemaker
var pickArray = [String]() // This is the list of values to show in the picker, set in the table VC
override func prepareForReuse() {
super.prepareForReuse()
textField?.text = nil
descriptionLabel?.text = nil
textLabel?.text = nil
textField?.placeholder = nil
unitPicker?.reloadAllComponents() // My picker
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView!, numberOfRowsInComponent component: Int) -> Int {
return pickArray.count // This seems to be where the problem is, or this is not being requested when the picker is drawn
}
func pickerView(_ pickerView: UIPickerView!, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
pickerLabel.font = UIFont(name: "Avenir Next Condensed", size: 22) /
pickerLabel.textAlignment = NSTextAlignment.center
pickerLabel.text = pickArray[row]
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int)
{
} // I'm using a button to process the selected value, so nothing here
}A portion of my table view controller:
func configureTableView() {
tblExpandable.register(UINib(nibName: "UnitsPickerCell", bundle: nil), forCellReuseIdentifier: "idCellUnitPicker")
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cellLabel = tableView.cellForRow(at: [indexPath.section, indexPath.row])?.textLabel?.text
let indexOfTappedRow = visibleRowsPerSection[indexPath.section][indexPath.row]
if (cellDescriptors[indexPath.section])[indexOfTappedRow] ["isExpandable"] as! Bool == true {
var shouldExpandAndShowSubRows = false
if (cellDescriptors[indexPath.section])[indexOfTappedRow]["isExpanded"] as! Bool == false {
shouldExpandAndShowSubRows = true
}
(cellDescriptors[indexPath.section])[indexOfTappedRow].updateValue(shouldExpandAndShowSubRows, forKey: "isExpanded")
for i in (indexOfTappedRow + 1)...(indexOfTappedRow + ((cellDescriptors[indexPath.section])[indexOfTappedRow]["additionalRows"] as! Int)) {
print("Looping through additional row \(i)")
((cellDescriptors[indexPath.section])[i]).updateValue(shouldExpandAndShowSubRows, forKey: "isVisible")
let unitPicker = (cellDescriptors[indexPath.section])[indexOfTappedRow]["secondaryTitle"] as! String
switch unitPicker {
case "Temperature":
pickArray = tempPickerUnits // These are all arrays of strings, 2-6 values each
case "Pressure":
pickArray = pressurePickerUnits
case "Humidity":
pickArray = humidityPickerUnits
case "Enthalpy":
pickArray = enthalpyPickerUnits
case "Volume":
pickArray = volumePickerUnits
default:
pickArray = tempPickerUnits
}
}
}
self.getIndicesOfVisibleRows()
self.tblExpandable.reloadSections(IndexSet(integer: indexPath.section), with: UITableViewRowAnimation.fade)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell if currentCellDescriptor["cellIdentifier"] as! String == "idCellUnitPicker" {
cell.pickArray = pickArray // This should tell the picker which array of strings to show
}
I did it !!
The key line of code is shown below. This ensures the picker asks the custom cell for the numberOfRowsInComponent each time the picker is called. The problem I was having was that the picker row text array was updating properly for each row in the viewForRow, but for some reason the number of rows was not updating each time the picker cell opened. This single line of code fixed that. Woohoo!
override func prepareForReuse() {
super.prepareForReuse()
textField?.text = nil
descriptionLabel?.text = nil
textLabel?.text = nil
textField?.placeholder = nil
unitPicker?.reloadAllComponents()
unitPicker?.dataSource = self // This is it !
}