1

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)
    }
}
Jon Bould
  • 65
  • 7
  • Not related but you can replace the **entire** code in `METDome_Lselect` with one line: `UserDefaults.standard.set("\(sender.selectedSegmentIndex + 1)", forKey: key_METDome_L)`. The last `else` will never be executed anyway. By the way, this is not Javascript or PHP. Variable names are supposed to be *camelCased* rather than *snake_cased*. – vadian Oct 14 '17 at 14:13
  • Thanks @vadian! I knew there must be a way to shorten the code, but never even thought of doing it the way you said so that's really helpful. However the else part is the code I really need to get working. I also tried `else if METDome_L.selectedSegmentIndex == UISegmentedControlNoSegment` however I guess that also will never be executed. On the note of variable names, for the most part I have used _camelCased_ I just used the underscore to separate parts of the variable to make it easier to recognise as I'm fairly new to coding and I can easily get lost with the amount of code. – Jon Bould Oct 14 '17 at 14:44
  • I doubt that the `IBAction` is triggered when the control is deselected programmatically. You could move the line to write `NONE` into the subclass – vadian Oct 14 '17 at 14:48
  • @vadian I had thought of that but I'm using this on multiple segmented controls so they would start to effect each other if I tried that. Unless I created a subclass for each segmented control, but that seems extremely inefficient. Also I checked what happens on the second tap and it looks like programmatically the selection that it being tapped is just set for a second time. – Jon Bould Oct 14 '17 at 14:52
  • Add a property `selectionKey` (or whatever appropriate) to the subclass and set it separately for each instance. Rather than using a `String` I would write an `Int` to `UserDefaults` without doing the math. Segment `1` (actually Segment `0`) is index `0` (almost all programming languages use zero-based indices) and `UISegmentedControlNoSegment` represents `-1` anyway. That makes it very easy to restore the values. – vadian Oct 14 '17 at 15:03
  • @vadian I'm not entirely sure what you are saying to do here. I get the segment index values but I'm confused with why you would write an `Int` to `UserDefaults` and what you are asking for to do with the `selectionKey`. Would you be able to supply some example code? – Jon Bould Oct 14 '17 at 15:15
  • I wrote an answer – vadian Oct 14 '17 at 15:35

1 Answers1

0

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)
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Brilliant! Did exactly what I needed! The only reason I was saving the `userDefaults` as _strings_ was because each segment is the number, and when I call it back on the next page it displays the number. But as you say, it's a lot more convenient to save them as _Int_ values and convert them later. – Jon Bould Oct 14 '17 at 16:07