How to Deselect a Segment in Segmented Control Button Permanently Till Its Clicked Again

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

Since UISegmentedControl only sends an action if a not selected segment is selected, you have to subclass UISegmentedControl to make a tiny change in its touch handling. I use this class:

@implementation MBSegmentedControl

// this sends a value changed event even if we reselect the currently selected segment
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger current = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if (current == self.selectedSegmentIndex) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}

@end

Now you will get UIControlEventValueChanged events even if the segment is already selected. Simply save the current index in a variable and compare it in the action. If the two indexes match you have to unselect the touched segment.

// _selectedSegmentIndex is an instance variable of the view controller

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_selectedSegmentIndex = self.segment.selectedSegmentIndex;
}

- (IBAction)segmentChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == _selectedSegmentIndex) {
NSLog(@"Segment %d deselected", sender.selectedSegmentIndex);
sender.selectedSegmentIndex = UISegmentedControlNoSegment;
_selectedSegmentIndex = UISegmentedControlNoSegment;
}
else {
NSLog(@"Segment %d selected", sender.selectedSegmentIndex);
_selectedSegmentIndex = sender.selectedSegmentIndex;
}
}

iOS 7 changed how touches are handled for UISegmentedControl. The selectedSegmentIndex is now changed during touchesEnded:.

So the updated Subclass should look like this:

@implementation MBSegmentedControl

+ (BOOL)isIOS7 {
static BOOL isIOS7 = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSInteger deviceSystemMajorVersion = [[[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."] objectAtIndex:0] integerValue];
if (deviceSystemMajorVersion >= 7) {
isIOS7 = YES;
}
else {
isIOS7 = NO;
}
});
return isIOS7;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if (![[self class] isIOS7]) {
// before iOS7 the segment is selected in touchesBegan
if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
// if the selectedSegmentIndex before the selection process is equal to the selectedSegmentIndex
// after the selection process the superclass won't send a UIControlEventValueChanged event.
// So we have to do this ourselves.
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
[super touchesEnded:touches withEvent:event];
if ([[self class] isIOS7]) {
// on iOS7 the segment is selected in touchesEnded
if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
}

@end

Swift 2.2 version, fixed the problem Grzegorz noticed.

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

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, withEvent: event)
if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.locationInView(self)
if CGRectContainsPoint(bounds, touchLocation) {
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}

Swift 3.0 changes the fix for this to look like the following:

class MyDeselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousIndex = selectedSegmentIndex

super.touchesEnded(touches, with: event)

if previousIndex == selectedSegmentIndex {
let touchLocation = touches.first!.location(in: self)

if bounds.contains(touchLocation) {
sendActions(for: .valueChanged)
}
}
}
}

Click on the already selected segment control swift

After lot of research am able to solve it.

Am writing answer here may it will be helpfull to others in future.

when the segment controll is pressed write below code.

@IBAction func segHeaderPressed(sender: UISegmentedControl) {

if segHeader.selectedSegmentIndex == 0{

let sortedViews = sender.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )

        for (index, view) in sortedViews.enumerate() {
if index == sender.selectedSegmentIndex {
view.backgroundColor = iOSBlueColor // UIColor.blueColor()
} else {
view.backgroundColor = UIColor.clearColor()
}
}

selectedInd = -1

//your code ...
}else if(segHeader.selectedSegmentIndex == 1){

        let sortedViews = sender.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )

for (index, view) in sortedViews.enumerate() {
if index == sender.selectedSegmentIndex {
view.backgroundColor = iOSBlueColor //UIColor.blueColor()
} else {
view.backgroundColor = UIColor.clearColor()
}
}

// your code ...
}else if(segHeader.selectedSegmentIndex == 2){

let sortedViews = sender.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )

        for (index, view) in sortedViews.enumerate() {
if index == sender.selectedSegmentIndex {
view.backgroundColor = iOSBlueColor //UIColor.blueColor()
} else {
view.backgroundColor = UIColor.clearColor()
}
}

}else if(segHeader.selectedSegmentIndex == 3){

let sortedViews = sender.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )

        for (index, view) in sortedViews.enumerate() {
if index == sender.selectedSegmentIndex {
view.backgroundColor = iOSBlueColor //UIColor.blueColor()
} else {
view.backgroundColor = UIColor.clearColor()
}
}

}
}

basically segmented controll functionality is once it is selected if you tap again same it will not respond. so am just changing the background colors of selected segment. hope it helps someone.

UISegmentedControl deselected is not recognised in code although it is visually

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)
}

Swift handle action on segmented control

You set your target to fire just when the value change, so if you select the same segment the value will not change and the popover will not display, try to change the event to TouchUpInside, so it will be fired every time you touch inside the segment

segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .touchUpInside)

UISegmentedControl register taps on selected segment

You can subclass UISegmentedControl, and then override setSelectedSegmentIndex:

- (void) setSelectedSegmentIndex:(NSInteger)toValue {
if (self.selectedSegmentIndex == toValue) {
[super setSelectedSegmentIndex:UISegmentedControlNoSegment];
} else {
[super setSelectedSegmentIndex:toValue];
}
}

If using IB, make sure you set the class of your UISegmentedControl to your subclass.

Now you can listen for the same UIControlEventValueChanged as you would normally, except if the user deselected the segment, you will see a selectedSegmentIndex equal to UISegmentedControlNoSegment:

-(IBAction) valueChanged: (id) sender {
UISegmentedControl *segmentedControl = (UISegmentedControl*) sender;
switch ([segmentedControl selectedSegmentIndex]) {
case 0:
// do something
break;
case 1:
// do something
break;
case UISegmentedControlNoSegment:
// do something
break;
default:
NSLog(@"No option for: %d", [segmentedControl selectedSegmentIndex]);
}
}

IOS Segmented Control: Can't Change Selected Segment

Whenever I see this it is almost always the same problem... If the segmented control is being drawn outside of its parent view, then it will not respond to taps. This is most likely the problem you are having. (This can also happen with UIButtons.)

Ways to test if this is the problem:

  1. set clipsToBounds of the segmented control's parent to true. If the control no longer shows on the screen, then it is being drawn outside of it's parent view. segmentedControl.superview?.clipsToBounds = true

  2. add a border to the segmented control's parent. If the control is being drawn outside the border then there you go. segmentedControl.superview?.layer.borderWidth = 1

Solve the problem by expanding the size of the parent view so the control is being drawn within it.


On a side note, your IBAction is incorrect. When the value changed action is called, the segmented control will already have changed its value and the new segment will be highlighted. You don't have to do it manually. This is another indicator that your control isn't being drawn in the bounds of its parent view.

UISegmentedControl: how to detect click on current segment?

The solution is to have a custom subclass of UISegmentedControl and check it yourself like this.

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
current = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];

if (current == self.selectedSegmentIndex)
[self sendActionsForControlEvents:UIControlEventValueChanged];
}

I had an other solution all in touchesBegan, but it's not working anymore in iOS 7. There is also other solution on Stack Overflow that are not working in iOS 6 and greater.



Related Topics



Leave a reply



Submit