Search code examples
iosswiftuisegmentedcontrolnsnotificationcenter

Notification is not working for UISegmentController


I have a tableView. I register the Cell as follow:

tableView.register(TVCellElementProperties.self, forCellReuseIdentifier: cellId1)

Inside TVCellElementProperties, I created manually a segment controller as follow:

    let unitTypeSegmentedControl: UISegmentedControl = {
    let types = ["Blue", "White"]
    let sc = UISegmentedControl(items: types)
    sc.selectedSegmentIndex = 0
    sc.translatesAutoresizingMaskIntoConstraints = false
    sc.tintColor = UIColor.darkBlue
    sc.addTarget(self, action: #selector(handleUnitChange), for: .valueChanged)

    return sc
}()

@objc func handleUnitChange() {
    NotificationCenter.default.post(name: .unitPicked, object: self)
}

So, I think when I change the value inside the SegmentController, it should redirect me to the function handleUnitChange()

inside the tableView, I inserted this line into the ViewDidLoad:

NotificationCenter.default.addObserver(self, selector: #selector(unitPicked), name: .unitPicked, object: nil)

When I run the application, the function ** handleUnitChange** inside tableviewCell is not called. What I did wrong? how do I know what I clicked ?

EDIT: I am calling a setupView Function which responsible for insert the Segment Controller inside the Cell from init inside the UITableViewCell as follow:

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    self.setupViews()
    print("test init")
}

So, when i run it, setupView is called just once, thus ** handleUnitChange** is called too just once. I mean to say, when the application is up and running, and when I click on the segment controller inside TableView, the function handleUnitChange is not called anymore.

I tried to call the function from the Cell inside CellForRow, but same as above. the function ** handleUnitChange** is not called overtime I clicked inside the Segment Controller.

if let cell = tableView.dequeueReusableCell(withIdentifier: cellId1, for: indexPath) as? TVCellElementProperties {

    //cell.backgroundColor = UIColor.rgb(red: 12, green: 122, blue: 12)
    //cell.contentView.isUserInteractionEnabled = false
    cell.setupViews()
    //print("\(cell.handleUnitChange(sender: u))")
    cell.handleUnitChange(sender: cell.unitTypeSegmentedControl)
    return cell
}

Solution

  • Step 1

    We first work on UITableViewCell.

    class ProfileTableViewCell: UITableViewCell {
        // MARK: - IBOutlet
        @IBOutlet var firstLabel: UILabel!
        @IBOutlet var lastLabel: UILabel!
        @IBOutlet var ageLabel: UILabel!
        @IBOutlet var pictureImageView: UIImageView!
        @IBOutlet weak var segmentControl: UISegmentedControl! // (a)
    
        // MARK: - awakeFromNib
        override func awakeFromNib() {
            super.awakeFromNib()
            firstLabel.backgroundColor = UIColor.clear
            lastLabel.backgroundColor = UIColor.clear
            ageLabel.backgroundColor = UIColor.clear
            segmentControl.addTarget(self, action: #selector(valueChanged), for: .valueChanged) // (b)
        }
    
        var onSegmentChanged: ((Int, Int) -> Void)? // (c)
    
        // (d)
        @objc func valueChanged(sender: UISegmentedControl) {
            onSegmentChanged!(sender.tag, sender.selectedSegmentIndex)
        }
    }
    

    First, add an IBOutlet connection (a) through Interface Builder. Set a selector target (b) through awakFromNib. In order to receive user's action through UITableView, you must send a signal with the closure (c). Finally, add a target receiver (c).

    Step 2

    We now work with UIViewController.

    class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            guard let people = fetchedResultsController.fetchedObjects else { return 0 }
            return people.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ProfileTableViewCell
            let person = fetchedResultsController.object(at: indexPath)
            cell.segmentControl.tag = indexPath.row // (e)
            // action //
            (f)
            cell.onSegmentChanged = { tag, selectedSegment in
                // (g) self.segmentTapped(tag: tag, index: selectedSegment)
                // print(tag, selectedSegment)
            }
            return cell
        }
    
        func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
            if editingStyle == .delete {
                // fetching data //
                let person = fetchedResultsController.object(at: indexPath)
    
                // deleting data //
                person.managedObjectContext?.delete(person)
    
                // saving the change //
                let context = persistentContainer.viewContext
                do {
                    try context.save()
                    print("saved...")
                } catch {
                    print("failed saving")
                }
            }
        }
    
        (h)
        func segmentTapped(tag: Int, index: Int) {
            print(tag, index)
        }
    }
    

    When you send user's action through UITableViewCell, you must receive it through UIViewController with UITableView's cellForRowAt delegate method. In order to see which segmentControl the user has selected, you set a tag (e). The closure (f) is set before returning the cell. You can print the result inside the closure. If you want to take the result out of the delegate method, see (g) and (h).

    Warning

    If you take a good look at line (e), indexPath.row is used as a tag. This approach works only if you know that none of the table rows is removed. Otherwise, your table data must have a dictionary value that gives each row a unique number.

    Test

    See how it works. When the user uses the segment control on the second row, you will receive a call through cellForRowAt. The result is 2 and 1, 2 as in the row, 1 as in the selectedSegmentIndex value. The user doesn't have to select any of the table rows.

    enter image description here