Enable/Disable UISlider when a SegmentControl is selected in Table cell

Hello, I am currently learning and developing my first iOS app and I have created a custom UITableViewCell using a XIB file in which I have integrated a UISlider and a SegmentedControl button.

What I want to accomplish is the first time the table loads up, the UISlider is disabled - I accomplish this in awakeFromNib and it also corresponds to the 0 value selection of the SegmentedControl. Then when a user touches "One" or "Two" on the SegmentedControl I want to enable the UISlider.

I am using a delegated protocol to send the indexPath of the selected cell to my ViewController:

extension ViewController: AccountCellDelegate {
    func segmentControlSelected(at index: IndexPath, segmentValue: Int, slider: UISlider) {
        slider.isEnabled = (segmentValue != 0)
        myTableView.reloadRows(at: [index], with: .none)
    }

The problems with this implementation are that I have to click twice on a SegmentedControl button to see its value selected and the UISlider still does not change its appeareance from "faded out - disabled" to normal - enabled unless I click on it. Do you have any idea if reloadRows(at:with:) is the correct way to refresh the cell so that the UISlider appeareance is correctly displayed?

Answered by Claude31 in 699496022

if reloadRows(at:with:) is the correct way to refresh the cell

reloadRows does what you have defined in

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

So please show this func in your code.

A common mistake is to think that the cell is a data storage. It is not.

So you have to store the status of slider in the dataSource. This dataSource is an array of some struct, like

struct MyCellData {
  var someText: String = ""
  var sliderOn: Bool = false
}
var myData: [MyCellData] = []

You initialize myData as needed in viewDidLoad, with the exact number of cells.

You have to change your func into

    func segmentControlSelected(at index: IndexPath, segmentValue: Int, slider: UISlider) {
        myData[index.row].sliderOn = (segmentValue != 0)
        myTableView.reloadRows(at: [index], with: .none)
    }

I assume you have defined an IBOulet for slider

@IBOutlet weak var slider : UISlider!

Then cellForRowAt should look like

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   // Fetch a cell of the appropriate type.
   let cell = tableView.dequeueReusableCell(withIdentifier: "cellTypeIdentifier", for: indexPath) as! MyCellType
   
   // Configure the cell’s contents.
   cell.textLabel!.text = myData[index.row].someText
   cell.slider.isEnabled = myData[index.row].sliderOn
       
   return cell
}
Accepted Answer

if reloadRows(at:with:) is the correct way to refresh the cell

reloadRows does what you have defined in

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

So please show this func in your code.

A common mistake is to think that the cell is a data storage. It is not.

So you have to store the status of slider in the dataSource. This dataSource is an array of some struct, like

struct MyCellData {
  var someText: String = ""
  var sliderOn: Bool = false
}
var myData: [MyCellData] = []

You initialize myData as needed in viewDidLoad, with the exact number of cells.

You have to change your func into

    func segmentControlSelected(at index: IndexPath, segmentValue: Int, slider: UISlider) {
        myData[index.row].sliderOn = (segmentValue != 0)
        myTableView.reloadRows(at: [index], with: .none)
    }

I assume you have defined an IBOulet for slider

@IBOutlet weak var slider : UISlider!

Then cellForRowAt should look like

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   // Fetch a cell of the appropriate type.
   let cell = tableView.dequeueReusableCell(withIdentifier: "cellTypeIdentifier", for: indexPath) as! MyCellType
   
   // Configure the cell’s contents.
   cell.textLabel!.text = myData[index.row].someText
   cell.slider.isEnabled = myData[index.row].sliderOn
       
   return cell
}

Thanks a lot for the reply Claude31 and for clarifying some aspects that were not very obvious for me.

I have tried implementing something along your line but I still have to press twice on my the SegmentedControl button to set the value of the button when I use myTableView.reloadRows(at: [index], with: .none) in func segmentControlSelected. Even so, the appeareance of the UISlider does not change. If I comment out reloadRows(at:with:), the SegmentedControl buttons set their values on the first click. Do you know why this happens?

My cellForRowAt func contains the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: K.cellNibName, for: indexPath) as! AccountHoldingsCell
        //Cell Delegate setup
        cell.delegate = self
        cell.cellIndexPath = indexPath
        ....
        cell.cellSlider.isEnabled = myData[indexPath.row]
        
        //Activity indicator
        self.activityIndicatorView.stopAnimating()
        
        return cell
}

Inside ViewControl class I have:

class ViewController: UIViewController {
   ....    
    var mergedBalance = ViewBalanceModel()
    var tempMergedBalance = ViewBalanceModel()
    
    var nbOfTableItems = 0
    var myData : [Bool] = []
    ...
}

myData is initialized inside a custom protocol that decodes API data and constructs a model in func

extension ViewController: ViewBalanceProtocol {
    
    func didUpdateBalanceTable(from manager: Manager, with accData: ViewBalanceModel) {
        .... //Get Api data into tempMergedBalance struct
        nbOfTableItems = tempMergedBalance.arrayOfItems.count

        myData = Array(repeating: false, count: nbOfTableItems) //Array setting initial values as False for UISlider

        DispatchQueue.main.async {
            self.mergedBalance = self.tempMergedBalance
            self.myTableView.refreshControl?.endRefreshing()
            self.myTableView.reloadData()
        }
    }

And of course:

extension ViewController: AccountCellDelegate {
    func segmentControlSelected(cell: UITableViewCell, at index: IndexPath, segmentValue: Int, slider: UISlider) {
        myData[index.row] = (segmentValue != 0)
        myTableView.reloadRows(at: [index], with: .none)
    }

Could you show code where you call segmentControlSelected ? Same question for didUpdateBalanceTable: do you call it only once at load or elsewhere in code (as Update in its name suggest). Because it restes the sliders.

And please, also post complete code of

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

@Claude31, your solutions was correct. I did some debugging and noticed that I was not updating all the data in my cell like you suggested. Mainly I was not setting the UISLider value in cellForRowAt but in the custom UITableViewCell. So now I know that reloadRows triggers cellForRowAt and I make sure that in cellForRowAt I always assign the cell contents: slider, labels, controls etc to a value from my array of myData structures containing the configurations/values of these elements. Thanks again!

One more thing hopefully useful for anyone, I was unable to use isEnabled property of the button to create the desired effect even with reloadRows but I managed to update the appearance of the slider with the following trick:

     cell.cellSlider.isUserInteractionEnabled = cellData[indexPath.row].sliderOn
    cell.cellSlider.alpha = cellData[indexPath.row].sliderOn == true ? 1.0 : 0.5

All the best!

Enable/Disable UISlider when a SegmentControl is selected in Table cell
 
 
Q