How to deselect a segment in a Segmented control button until it is pressed again

I have a UISegmentedControl with 4 segments. When selected, it must support selected state . When the same segment is clicked again, it must deselect itself . How to achieve this?

+28
ios objective-c uibutton uisegmentedcontrol
Jul 15 '13 at 11:05
source share
8 answers

Since the UISegmentedControl only sends an action, if an unselected segment is selected, you must subclass the UISegmentedControl to make minor changes to its touch processing. I am using 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 

You will now receive UIControlEventValueChanged events, even if a segment is already selected. Just store the current index in a variable and compare it in action. If both indices match, you must cancel the selected 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 has changed how strokes are processed for UISegmentedControl. The selected SegmentIndex parameter now changes 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 version 2.2, fixed the problem that 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 modifies the fix for this to look like this:

 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) } } } } 
+80
Jul 15 '13 at 11:17
source share

Here is the fix for the problem, when you try to cancel the selection by clicking on the UISegmentControl, and then you finish tapping outward - it still cancels the selection.

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint locationPoint = [[touches anyObject] locationInView:self]; CGPoint viewPoint = [self convertPoint:locationPoint fromView:self]; if ([self pointInside:viewPoint withEvent:event]) { int oldValue = self.selectedSegmentIndex; [super touchesEnded:touches withEvent:event]; if (oldValue == self.selectedSegmentIndex) { [super setSelectedSegmentIndex:UISegmentedControlNoSegment]; [self sendActionsForControlEvents:UIControlEventValueChanged]; } } } 
+6
Jan 30 '14 at 2:54
source share

You can do this with the following (thanks to Grzegorz 's answer and Matthias's answer ):

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex; [super touchesEnded:touches withEvent:event]; CGPoint locationPoint = [[touches anyObject] locationInView:self]; CGPoint viewPoint = [self convertPoint:locationPoint fromView:self]; if ([self pointInside:viewPoint withEvent:event] && previousSelectedSegmentIndex == self.selectedSegmentIndex) { self.selectedSegmentIndex = UISegmentedControlNoSegment; [self sendActionsForControlEvents:UIControlEventValueChanged]; } } 

I made an open source (MIT Licensed) class STASegmentedControl (supports iOS 7+) that has this function baked in (and more).

+2
Apr 28 '15 at 5:17
source share

Very useful! Thank! I wanted to have a little more control over events for my project, so I adapted the @Matthias response to send the custom event β€œValue without changes”. I gave an example of GitHub .

I also included the @Grzegorz fix so that it behaves correctly if the user drags his finger outside the segmented control.

+1
Apr 02 '14 at 1:10
source share

Here is a solution that is independent of the iOS version. He chooses behavior.

 @interface CustomSegmentedControl : UISegmentedControl @end @implementation CustomSegmentedControl{ BOOL _touchBegan; BOOL _reactOnTouchBegan; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { _touchBegan = YES; NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex; [super touchesBegan:touches withEvent:event]; if (_reactOnTouchBegan) { // before iOS7 the segment is selected in touchesBegan if (previousSelectedSegmentIndex == self.selectedSegmentIndex) { [self sendActionsForControlEvents:UIControlEventValueChanged]; } } } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { _touchBegan = NO; NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex; [super touchesEnded:touches withEvent:event]; if (!_reactOnTouchBegan) { CGPoint locationPoint = [[touches anyObject] locationInView:self]; CGPoint viewPoint = [self convertPoint:locationPoint fromView:self]; if ([self pointInside:viewPoint withEvent:event]) { // on iOS7 the segment is selected in touchesEnded if (previousSelectedSegmentIndex == self.selectedSegmentIndex) { [self sendActionsForControlEvents:UIControlEventValueChanged]; } } } } - (void)sendActionsForControlEvents:(UIControlEvents)controlEvents { if(controlEvents == UIControlEventValueChanged){ _reactOnTouchBegan = _touchBegan; } [super sendActionsForControlEvents:controlEvents]; } @end 
+1
Jul 30 '14 at 11:27
source share

Regarding the answer posted by @Matthias Bauch. I had to make small changes according to Swift 2.2 in Xcode 7.3:

 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) } } } } } 
+1
Aug 11 '16 at 14:37
source share

quick 3.1 version posted by @Kushal Ashok

 class ReselectableSegmentedControl: 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) } } } } } 
+1
Jan 13 '17 at 16:23
source share

Regarding @Stunner, this is my contribution to the goal. I changed something and added the _previousSelectedSegmentIndex property; in @Stunner code, the previousSelectedSegmentIndex variable was useless:

 @implementation STASegmentedControl { NSInteger _previousSelectedSegmentIndex; } - (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex { [super setSelectedSegmentIndex: selectedSegmentIndex]; _previousSelectedSegmentIndex = self.selectedSegmentIndex; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; CGPoint locationPoint = [[touches anyObject] locationInView:self]; CGPoint viewPoint = [self convertPoint:locationPoint fromView:self]; if (self.toggleableSegments) { // toggle selected segment on/off if ([self pointInside:viewPoint withEvent:event] && _previousSelectedSegmentIndex == self.selectedSegmentIndex) { self.selectedSegmentIndex = UISegmentedControlNoSegment; [self sendActionsForControlEvents:UIControlEventValueChanged]; } } _previousSelectedSegmentIndex = self.selectedSegmentIndex; } 
0
Apr 21 '17 at 14:45
source share



All Articles