UILabels of three UIPickerViews revert to previous value (sometimes) and change without selection.

I've a grid table using a UICollectionView - 4 columns x 4 rows. In the top row, I've three UIPickerViews. If the user selects a new value in the picker, the UILabels in the pickers revert to previous label (columns 1 and 4, not 3), and change the label.text on other pickers.


In columns 1 and 4, while the selection calls the delegate method, the UILabel rolls back to the previous value; and the label of the other picker (col. 1 or 4 - NOT 3) updates to the value selected in the selected picker (without changing the values in the not selected column).


In column 3, the picker appears to work perfectly - delegate called correctly and UILabel remains on the selected value. But if one or both of the labels in columns 1 or 4 have been changed from the default or first value in array, then these labels (in col.s 1 and 4) exchange values (when the picker in col. 3 is changed).


Within a custom UITableViewCell, managing a UICollectionView, I instantiate my custom UICollectionViewCell with a picker in it:


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        
        var cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseI4CVCell, for: indexPath) as! CVCell
        
        switch indexPath.item
        {
            case 0, 2, 3:
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseI4PickerCVCell, for: indexPath) as! PickerCVCell
                cell.caseOfPicker = indexPath.item
                cell.reference = self
                return cell
class PickerCVCell: UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource
{
    var reference: EgPickerDelegate?
    var caseOfPicker: Int?
    
    private var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?     // controller for fetched results
    
    @IBOutlet weak var pickerInCVCell: UIPickerView!
    
    override func awakeFromNib()
    {
        super.awakeFromNib()
        
        configureFetchedResultsController()
        
        pickerInCVCell.delegate = self
        pickerInCVCell.dataSource = self
    }
    
    private func configureFetchedResultsController()
    {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate
            else { return }
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Gender")
        let sortDescriptor = NSSortDescriptor(key: "sortOrder", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                    managedObjectContext: appDelegate.persistentContainer.viewContext,
                                                              sectionNameKeyPath: nil,
                                                              cacheName: nil)
        fetchedResultsController?.delegate = self as? NSFetchedResultsControllerDelegate
        
        do { try fetchedResultsController?.performFetch() }
        catch { print(error.localizedDescription) }
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int
    { return 1 }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
    {
        if let objects = fetchedResultsController?.fetchedObjects
        { return objects.count }
        return 4
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView
    {
        var pickerLabel: UILabel? = (view as? UILabel)
        let path = NSIndexPath(item: row, section: 0)
        if let gender = fetchedResultsController?.object(at: path as IndexPath) as? Gender
        {
            let text = gender.text ?? "Text not found"

            if pickerLabel == nil
            {
                pickerLabel = UILabel()
                pickerLabel?.textColor = UIColor.black
                pickerLabel?.textAlignment = NSTextAlignment.center
                pickerLabel?.font = pickerLabel?.font.with( .traitBold, .traitItalic)
            }
            pickerLabel?.text = text
        }
      
        return pickerLabel!
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
    { reference?.myPickerDidSelectRowInCase(selectedRowValue: row, caseOfPicker: caseOfPicker) }
    
    func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat
    { return CGFloat(120.0) }
}

The custom collectionView pickerView didSelectRow delegate method passes the correct caseOfPicker value after selection.


I've tried instantiating cells in different ways - to distinguish them as objects. I've tried using pickerView titleForRow instead of viewForRow, but it behaves the same way. Can't find a helpful dataSource method.

Accepted Reply

You will have to restructure your code in depth, because it is really messy in some places.

Picker 0 relates to cell for label 4, and that is in fact hard coded in your code. Don't keep it that way


I made it work by changing like this (just a hack to show a direction, did keep the fixed link between piucker and label through item numbers)


extension TableViewCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, PickerDelegate {
    func myPickerChangedValue(selectedRowValue: Int?, whichPicker: Int?)  {
        switch whichPicker  {
        case 0:          // you have to set the labvel here
            collectionViewLabelTextColumn1 = "changed col 1)"
            let indexPath = IndexPath(item: 4, section: 0)     // 4 because cell 4 correponds to picker 0
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn1
        case 2:
            collectionViewLabelTextColumn3 = "changed col 3)"
            let indexPath = IndexPath(item: 6, section: 0)     // 6 because cell 6 correponds to picker 2
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn3
        case 3:
            collectionViewLabelTextColumn4 = "changed col 4)"
            let indexPath = IndexPath(item: 7, section: 0)     // 7 because cell 7 correponds to picker 3
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn4
        default:
            collectionViewLabelTextColumn2 = "shouldnT change"
          //     What are the possible default values ? Only 2 ?
            let indexPath = IndexPath(item: 5, section: 0)     // 5 because cell 2 correponds to no picker (1)
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn2
        }
//            myCollectionView.reloadData()     // DON'T RELOAD, because it clears everything
    }

Replies

Seems code has been truncated.


What happens here when not case 0, 2, 3 : ?


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    { 
         
        var cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseI4CVCell, for: indexPath) as! CVCell 
         
        switch indexPath.item 
        { 
            case 0, 2, 3: 
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseI4PickerCVCell, for: indexPath) as! PickerCVCell 
                cell.caseOfPicker = indexPath.item 
                cell.reference = self 
    
            return cell

In addition, if you could sketch the UI here, and explain what does not work, that would help.


Where are the pickers ?

Where are the UILabels ?

______________________________________________________________________________

| | | | |

| | | | |

| | | | |

|___________________|__________________|____________________|___________________|

Thanks Claude for taking a look. This is the grid table with the pickers in the top row. The user should be able to pick a different gender (eg, Masculine, Neutral or Feminine) for each column, and the values below, in that column, should update (of course, the picker should also show the selected value - which is the problem).


| Masculine | | Masculine | Masculine |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Der Mann | gibt | dem Hund | den Ball. |

additional rows of examples


The UILabels that behave strangely are the 3 picker labels in the top row.


The cases, 4...28, are for the attributed text (put together in a separate method). The rest of the cellForItem, is simply:

            case 1:
                cell.label.text = nil
            case 4...32:
                cell = cVCellContents(cell: cell, indexPath: indexPath, offset: 0)
            default:
                cell.label.attributedText = NSAttributedString(string: "No case")
        }
        return cell
    }


To demo what I mean about the picker labels, if I roll the column 1 picker to 'Neutral', the data for the column updates (correct caseOfPicker value passed to delegate) to show neutral words in that column (eg, Das Kind), but the UILabel in column 1 reverts back to 'Masculine' while the label of the picker in the untouched 4th column changes from Masculine to Neutral (the data in this column stays masculine). In the other column (3), the picker label is unchanged.


| Masculine | | Masculine | Neutral |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Das Kind | gibt | dem Hund | den Ball. |


Continuing from this situation, if I then select Feminine in column 1, the data in this column updates with feminine values (eg Die Frau), the picker label in column 4 changes to Feminine, and the picker label in column 1 now updates to Neutral (i.e. the previously selected value for this column).


| Neutral | | Masculine | Feminine |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Die Frau | gibt | dem Hund | den Ball. |


This pattern repeats - with the column 4 label showing the UILabel selected in column 1, and column 1 reverting to its previously selected value.


Column 3 behaves slightly differently. When I pick another value, the data in column 3 updates correctly, and the picker in this column does NOT revert to its previous value (it correctly shows the selected value). Meanwhile, however, the labels in columns 1 and 4 swap values. In the example, continuing from above, column 3 is changed from Masculine to Neutral - the data updates correctly for the column (dem Pferd); column 3 label is correctly showing the selected Neutral value; but columns 1 and 4 exchange their values with each other (without changing their data).


| Feminine | | Neutral | Neutral |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Die Frau | gibt | dem Pferd | den Ball. |


Quantum computing?

Could you post code of myPickerDidSelectRowInCase ?


As well as the definition of EgPickerDelegate and fetchedResultsController class definition.


Can you confirm that collectionView is in a VC that conforms to EgPickerDelegate ?


Best would be to post the complete code for all the involved classes.

myPickerDidSelectRowInCase could be anything that changes the results only for items in the column. Simplest would be to select actual values for each column based on caseOfPicker. In the code below, which is more complex, i'm setting a column specific delegate that is used to fetch data before ereloading the collection view.


protocol EgPickerDelegate
{ func myPickerDidSelectRowInCase(selectedRowValue: Int?, caseOfPicker: Int?) }


extension EgEndingsCVTVCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, EgPickerDelegate
{
    func myPickerDidSelectRowInCase(selectedRowValue: Int?, caseOfPicker: Int?)
    {
        switch caseOfPicker
        {
        case 0:
            col0GenderPredicate = genderPredicator(selectedRowValue: selectedRowValue ?? 0)
        case 2:
            col2GenderPredicate = genderPredicator(selectedRowValue: selectedRowValue ?? 0)
        case 3:
            col3GenderPredicate = genderPredicator(selectedRowValue: selectedRowValue ?? 0)
        default:
            col0GenderPredicate = genderPredicator(selectedRowValue: 0)
        }
        loadSavedData()
        cView.reloadData()
    }

as above - confirm - the collection view cell class confirms to the EgPickerDelegate.


I'm not sure if you mean the fetchedResultsControllerDelegate. I'm not sure this is useful.


extension EgEndingsCVTVCell: NSFetchedResultsControllerDelegate
{
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
    {
        print("THE CONTROLLER CONTENT HAS CHANGED")
        cView.reloadData()
    }
}


The CollectionView is actually inside a TableViewCell. At cell for item at row:


        else if indexPath.row == 5
        {
            let cell = tableView.dequeueReusableCell(withIdentifier: "EgEndingsCVTVCell") as! EgEndingsCVTVCell
            return cell
        }


Thanks for looking into this.

It is a bit difficult to be sure without possibility to test code.


In class PickerCVCell, you have a pickerView defined in IBOutlet.

What is this IBOutlet connected to ?

Do you define the cell from a nib ? Or is the picker defined in the prototype ?


To check, you could give a tag to the pickers and print the tag value in pickerView didSelectRow.


A safe way to do it would be to create the picker instance programmatically in the cell when you create the cell (awakeFromNib), and not define as an IBOutlet.

Before changing my approach completely to instantiate the pickers programatically, I thought to recreate the issue in a new project - to see if it repeats itself. And it does!


It seems a strange enough issue - could you please look at this code and see if it is something I am doing wrong? It is not exactly as described above, but chosing a label updates the correct collection view, but the picker labels behave strangely.


UIPickerLabelThing.zip

I tested your code, but pickers do not show.


There are also a lot of error messages for layout. Maybe the picker view is too small for correct display of UIPickerView ?

Anyway, it is not possible to test as posted.

I downloaded the file and the pickers show on a iPhone 7 + device, a simulator iPhone Xr, and every other simulator tested in xcode 10.3. iPad Air is the best to show the below situation. The values in the pickers are "value 1" or "value 2", and can be changed with three fingers in the simulator.


| Value 1 | nothing | Value 1 | Value 1 |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Initial text 1 | nothing | Initial text 3 | Initial text 4 |


I also see the contraint errors, but there is nothing fatal or overriding the collection view size for item method (pickers are shown - maybe it is not clear that they are pickers because the text is similar to the other labels.


As above, if I use the picker in the first column to select value 2, the data in that column updates, but the picker itself reverts to its former value - Value 1 - and the picker in the third column changes its display to Value 2 (no change to the data in that column).


| Value 1 | nothing | Value 2 | Value 1 |

-- -- -- -- -- -- --- -- -- -- -- --- -- - -- --- - - -- -

| Ichanged col 1) | nothing | Initial text 3 | Initial text 4 |

I tested on simulator for iPad.


I see the labels, but when I click on it, I do not see the picker. However, I can change the value by clicing "randomly"

In fact, if I click and move mouse, I see the picker values.


But definetely, the picket rect is not high enough, that's awfull to use !


I will try to investigate anyway

You will have to restructure your code in depth, because it is really messy in some places.

Picker 0 relates to cell for label 4, and that is in fact hard coded in your code. Don't keep it that way


I made it work by changing like this (just a hack to show a direction, did keep the fixed link between piucker and label through item numbers)


extension TableViewCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, PickerDelegate {
    func myPickerChangedValue(selectedRowValue: Int?, whichPicker: Int?)  {
        switch whichPicker  {
        case 0:          // you have to set the labvel here
            collectionViewLabelTextColumn1 = "changed col 1)"
            let indexPath = IndexPath(item: 4, section: 0)     // 4 because cell 4 correponds to picker 0
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn1
        case 2:
            collectionViewLabelTextColumn3 = "changed col 3)"
            let indexPath = IndexPath(item: 6, section: 0)     // 6 because cell 6 correponds to picker 2
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn3
        case 3:
            collectionViewLabelTextColumn4 = "changed col 4)"
            let indexPath = IndexPath(item: 7, section: 0)     // 7 because cell 7 correponds to picker 3
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn4
        default:
            collectionViewLabelTextColumn2 = "shouldnT change"
          //     What are the possible default values ? Only 2 ?
            let indexPath = IndexPath(item: 5, section: 0)     // 5 because cell 2 correponds to no picker (1)
            let cell = myCollectionView.cellForItem(at: indexPath) as! CollectionViewCell
            cell.label.text = collectionViewLabelTextColumn2
        }
//            myCollectionView.reloadData()     // DON'T RELOAD, because it clears everything
    }

I see - basically reloading the collection view - with the UIPickerViews inside it - was confusing the pickers. That makes sense to me now. I've found a way, based on your code, to update the existing cells and their labels to avoid this problem. Wonderful - thank you very much!