Search code examples
iosswiftxcodensuserdefaultsuisegmentedcontrol

UISegmentedControl deselected is not recognised in code although it is visually


I have a 5 segment segmentedControl which I've subclassed using the code from this post:

How to deselect a segment in Segmented control button permanently till its clicked again

to allow the controller to be deselected when the selected segment is touched for a second time.

This works visually but when trying to assign a UserDefault it's just recognised as the segment that was touched twice.

I can't figure out what I can add to either the subclass code, or the viewController code to make this work.

Any help would be appreciated.

SUBCLASS CODE:

class mySegmentedControl: UISegmentedControl {
    @IBInspectable var allowReselection: Bool = true

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let previousSelectedSegmentIndex = self.selectedSegmentIndex
        super.touchesEnded(touches, with: event)
        if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
            if let touch = touches.first {
                let touchLocation = touch.location(in: self)

                if bounds.contains(touchLocation) {
                    self.sendActions(for: .valueChanged)
                    self.selectedSegmentIndex = UISegmentedControlNoSegment
                }
            }
        }
    }

}

VIEWCONTROLLER CODE:

@IBOutlet weak var METDome_L: UISegmentedControl!
let key_METDome_L = "METDome_L"
var METD_L: String!

@IBAction func METDome_Lselect(_ sender: Any) {
    if METDome_L.selectedSegmentIndex == 0{
        METD_L = "1"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
    else if METDome_L.selectedSegmentIndex == 1{
        METD_L = "2"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
    else if METDome_L.selectedSegmentIndex == 2{
        METD_L = "3"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
    else if METDome_L.selectedSegmentIndex == 3{
        METD_L = "4"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
    else if METDome_L.selectedSegmentIndex == 4{
        METD_L = "5"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
    else{
        METD_L = "NONE"
        UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
    }
}

Solution

  • First of all if you are subclassing the control you have to use that type to get the enhanced functionality.

    @IBOutlet weak var METDome_L: MySegmentedControl! // class names start with a capital letter
    

    Add a property selectionKey and save the state UISegmentedControlNoSegment (as Int) in UserDefaults when the control is deselected. A fatal error is thrown if the property is empty (it has to be set in the view controllers which use the subclass).

    class MySegmentedControl: UISegmentedControl {
        @IBInspectable var allowReselection: Bool = true
    
        var selectionKey = ""
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            let previousSelectedSegmentIndex = self.selectedSegmentIndex
            super.touchesEnded(touches, with: event)
            if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
                if let touch = touches.first {
                    let touchLocation = touch.location(in: self)
    
                    if bounds.contains(touchLocation) {
                        self.sendActions(for: .valueChanged)
                        self.selectedSegmentIndex = UISegmentedControlNoSegment
                        guard !selectionKey.isEmpty else { fatalError("selectionKey must not be empty")
                        UserDefaults.standard.set(UISegmentedControlNoSegment, forKey: selectionKey)
                    }
                }
            }
        }
    }
    

    In your view controller set the property selectionKey in viewDidLoad to the custom key and save the selected state of the control to UserDefaults in the IBAction. Do not any math. The first segment is segment 0 with index 0. Get used to zero-based indices. That makes it more convenient to restore the selected state.

    @IBOutlet weak var METDome_L: MySegmentedControl!
    let key_METDome_L = "METDome_L"
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        METDome_L.selectionKey = key_METDome_L
    }
    
    @IBAction func METDome_Lselect(_ sender: UISegmentedControl) {
        UserDefaults.standard.set(sender.selectedSegmentIndex, forKey: key_METDome_L)
    }