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:
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
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
Uipopovercontroller Dealloc Reached While Popover Is Still Visible
--Resource-Rules Has Been Deprecated in MAC Os X >= 10.10
Uiview Atop the Keyboard Similar to Imessage App
Tableview Reloaddata VS. Beginupdates & Endupdates
Opengl Es 2.0 Object Picking on iOS
Swift: Testing Optionals for Nil
iOS 13 - How to Login in In-App Purchase Sandbox Account
What Do JSONserialization Options Do and How Do They Change JSONresult
iPhone Uitextfield Background Color
Printing the View in iOS with Swift
How to Create a Release Build in Xcode
Dynamic Uiimageview Size Within Uitableview
Uiviewcontrollerhierarchyinconsistency When Trying to Present a Modal View Controller
How to Cache Resources Loaded in an iPhone Uiwebview
Convert JSON String to JSON Object in Swift 4