UIButton and UIControlEventState issue - iphone

I'm having a very specific "bug" in my iPhone application. I'm setting two images for the highlighted and normal states of a button. It works as expected when you "press" and then "touch up" at a slow pace, but if you click/tap it quickly, there's a noticeable flicker between states. Is this a known bug or am I setting the states incorrectly?
Here's the code that creates the buttons:
UIImage *normalImage = [[UIImage imageNamed:#"btn-small.png"] stretchableImageWithLeftCapWidth:10.0f topCapHeight:0.0f];
UIImage *highlightedImage = [[UIImage imageNamed:#"btn-small-down.png"] stretchableImageWithLeftCapWidth:10.0f topCapHeight:0.0f];
[self setBackgroundColor:[UIColor clearColor]];
[self setBackgroundImage:normalImage forState:UIControlStateNormal];
[self setBackgroundImage:highlightedImage forState:UIControlStateDisabled];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];
[self setAdjustsImageWhenDisabled:FALSE];
[self setAdjustsImageWhenHighlighted:FALSE];
When a button is tapped it simply disables itself and enables the other button:
- (IBAction)aboutButtonTouched:(id)sender
{
aboutButton.enabled = FALSE;
rulesButton.enabled = TRUE;
}
- (IBAction)rulesButtonTouched:(id)sender
{
rulesButton.enabled = FALSE;
aboutButton.enabled = TRUE;
}
Any thoughts on this quick-click flicker?

Ok, I resolved this. Took a bit of reverse engineering what I was trying to do, but I thought I'd post what I did in case it helps someone else.
The first thing I did was modify the aboutButtonTouched method to log the button's state property which is a bit-mask NSUInteger:
- (IBAction)aboutButtonTouched:(id)sender
{
rulesButton.enabled = TRUE;
[sender setEnabled:FALSE];
NSLog(#"%d", [sender state]);
}
At this point, the button is disabled through setEnabled, and the log reported that the state was "3". Looking at the bit-mask type for UIControlState:
enum {
UIControlStateNormal = 0, // 0
UIControlStateHighlighted = 1 << 0, // 1
UIControlStateDisabled = 1 << 1, // 2
UIControlStateSelected = 1 << 2, // 4
UIControlStateApplication = 0x00FF0000,
UIControlStateReserved = 0xFF000000
};
(Notes added since I can never remember bitwise). We can see that to get "3" (0011) we should use UIControlStateHighlighted | UIControlStateDisabled (0001|0010 or 1|2), something which I did not have as a state in my original button definition. The key here that there's a brief time when the state is both before just being disabled ("A control enters this state when a touch enters and exits during tracking and and when there is a touch up" -- from the docs). So the final state settings for the button where it does not flicker are:
[self setBackgroundImage:normalImage forState:UIControlStateNormal];
[self setBackgroundImage:highlightedImage forState:UIControlStateDisabled];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted|UIControlStateDisabled];

To be noted that, although not documented, all state combinations that contain UIControlStateHighlighted|UIControlStateDisabled aren't valid: these are equivalent to same without UIControlStateDisabled.
In short:
(UIControlStateHighlighted | UIControlStateDisabled) == UIControlStateHighlighted
and
(UIControlStateSelected | UIControlStateHighlighted | UIControlStateDisabled) == (UIControlStateSelected | UIControlStateHighlighted)
I found that the hard way: was setting some attributes for the Highlighted+Disabled state which was overwriting my settings for the Highlighted state. Spent half a day to track down the issue why button wasn't properly highlighting...

Maybe you should reverse the sequence of changing the buttons
- (IBAction)aboutButtonTouched:(id)sender
{
rulesButton.enabled = TRUE;
aboutButton.enabled = FALSE;
}
When you hide the one button first and then show the other, there's maybe a little gap, which creates the flicker. I think it's better to show the other button first.

Related

Distinguish if UIsegmentedontrol value change is from user or from system

Hello I have a UISegmentedControl with two segments. The selected segment is modified programmatically in some case and by the user in some other. I only want to trigger the selector when the change is due to a user action(only when user actually press the segmented control and not when the system do segmentedControl.selectedSegmentIndex = ...). Any idea?
If you do
[self.segment setSelectedSegmentIndex:1];
This will not call the action of valueChanged on the segment, so what is your question?
[segmentedControl addTarget:self action:#selector(segmentAction:) forControlEvents: UIControlEventValueChanged];
- (IBAction)segmentAction:(id)sender {
// valuechanged connected function
UISegmentedControl *segControll = (UISegmentedControl *)sender;
if (segControll.tag == 0) {
}
else {
isProgramaticallyChanged = NO; //important
}
}

Unable to keep UIButton in selected state after TouchUpInside Event

I have the need for an UIButton to maintain a pressed state. Basically, if a button is in a normal state, I want to touch the button, it highlight to its standard blue color and then stay blue after lifting my finger.
I made the following UIAction and connected the buttons Touch Up Inside event to it.
-(IBAction) emergencyButtonPress:(id) sender
{
if(emergencyButton.highlighted)
{
emergencyButton.selected = NO;
emergencyButton.highlighted = NO;
}
else
{
emergencyButton.selected = YES;
emergencyButton.highlighted = YES;
}
}
But what happens, after I remove my finger, the button goes back to a white background. For a test I added a UISwitch and have it execute the same code:
-(IBAction) emergencySwitchClick:(id) sender
{
if(emergencyButton.highlighted)
{
emergencyButton.selected = NO;
emergencyButton.highlighted = NO;
}
else
{
emergencyButton.selected = YES;
emergencyButton.highlighted = YES;
}
}
In this case the button toggles to a highlighted and non-highlighted state as I would expect.
Is there another event happening after the Touch Up Inside event that is resetting the state back to 'normal'? How do I maintain the highlighted state?
The highlighted state is applied and removed by iOS when you touch / release the button. So don't depend on it in your action method.
As one of the other answers says, set the highlighted image to be the same as the selected image, and then modify your action method:
-(IBAction) emergencyButtonPress:(id) sender
{
emergencyButton.selected = !emergencyButton.selected;
}
If you want something like a toggle button, customize your "selected" state to be your "pressed" state, and then write something like this:
- (IBAction)buttonTapped:(id)sender {
[(UIButton*)sender setSelected:![sender isSelected]];
}
if you want the above code to work, you should set an image for the state selected and one (even the same) for he state highlighted.
[emergencyButton setBackgroundImage:buttonHighlightImage forState:UIControlStateHighlighted];
[emergencyButton setBackgroundImage:buttonHighlightImage forState:UIControlStateSelected];
hope it helps.
I had this same problem, and this is what worked for me:
-(IBAction)buttonPressed:(id)sender {
if (isSelected) {
[_button setSelected:NO];
[_button setImage:[UIImage imageNamed:#"not_selected.png"] forState:UIControlStateSelected];
isSelected=NO;
}
else {
[_button setSelected:YES];
[_button setImage:[UIImage imageNamed:#"selected.png"] forState:UIControlStateSelected];
isSelected=YES;
}
}
This is not possible unfortunately, as soon as you let go Apple changes the state again.
One option I've used before (but not pretty) is that you use a performSelector with delay 0.1 after the button is pressed to put it again in the selected state. This did work in my case.
EDIT: If you don't want to see the blinking effect, just set the delay to 0.0
This worked for me:
- (void)tapButton:(id)sender{
UIButton *button = sender;
if (!button.selected){
[self performSelector:#selector(highlight:) withObject:button afterDelay:0.0];
}else{
[self performSelector:#selector(removeHighlight:) withObject:button afterDelay:0.0];
}
}
- (void)highlight:(UIButton *)button{
button.selected = !button.selected;
button.highlighted = YES;
}
- (void)removeHighlight:(UIButton *)button{
button.selected = !button.selected;
button.highlighted = NO;
}
Try this simple answer....
- (void)mybutton:(id)sender
{
UIButton *button = (UIButton *)sender;
button.selected = ![button isSelected]; // Important line
if (button.selected)
{
NSLog(#"Selected");
NSLog(#"%i",button.tag);
}
else
{
NSLog(#"Un Selected");
NSLog(#"%i",button.tag);
}
}

iPhone: Button Level

i'm new to iPhone.Is there any way to set 3 images to button, and when I will click the button it will change button image in a circle ? Thanks...
In your IBAction method for the button click, you can keep a count of which image you are on, and also have all 3 UIImages ready to go (so there isn't the load everytime..unless the images are large (which they shouldn't be for a button) and check it and rotate it basically with something simple like (assumes you have 3 instance variables of UIImages ready that are already initialized with the images.:
-(IBAction) myButtonPress:(id)sender {
int imageCounter = 0;
if(imageCounter == 0) {
[myButtonImage setBackgroundImage:image1 forState:UIControlStateNormal];
imageCounter++;
}
else if(imageCounter == 1) {
[myButtonImage setBackgroundImage:image2 forState:UIControlStateNormal];
imageCounter++;
}
else if(imageCounter == 2) {
[myButtonImage setBackgroundImage:image3 forState:UIControlStateNormal];
imageCounter = 0;
}
//Do other button press stuff
}

Hiding or moving SegmentContoller

Hello I've tried for 3 weeks to solve this issue and it stumps me. What i am trying to do is create a 3 part segment from an array, display it in a view in a certain position, then remove it from view when the "OFF" flag is set. Every thing works except the removal of the segment. It will even commuticate with (pickOne) and display the segment letters in a label. What i can't get to work is either of the two: setHidden:YES, or removeAllSegments. Any help would be appreciated. Here is my code.
- (void) showSegment {
int x = 192;
int y = 212;
int w = 125;
int h = 25;
SegUnit1 = #"A";
SegUnit2 = #"B";
SegUnit3 = #"C";
threeSegs = [NSArray arrayWithObjects: SegUnit1, SegUnit2, SegUnit3, nil];
segSize = [NSArray arrayWithArray:threeSegs];
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
if ([segmentState_height isEqualToString:#"ON"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
} else if ([segmentState_height isEqualToString:#"OFF"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
[heightSC setHidden:YES]; // NSLog showing "OFF" but segment will not hide.
[heightSC removeAllSegments]; // NSLog showing "OFF" and segment is suppose to dismantle and does not.
}
}
I know now that i have to "not" create and remove in the same function, and was given a tip on correcting this but I don't know how to use the tip.
here is what was suggested.
Well, your method is a little confused, since you are trying to both create and hide at the same time. So you might consider splitting that up into separate methods.
In general, it will be along these lines:
Code:
if ([self theControlProperty] == nil)
{
UISeg... *theControl = [[UISeg alloc] ....];
[self setTheControlProperty:theControl];
...
}
if (shouldHideTheControl)
{
[[self theControlProperty] setHidden:YES];
}
Any help would be appreciated.
The problem you have is that you're creating a new UISegmentedControl instance every time that method is called. The first time through, you create an instance and add it as a subview to your view. This apparently works fine, as it should. Then the method returns, and you no longer have any easy way to refer to that instance that you created. When you re-enter -showSegment, you create a different instance, and then hide and/or destroy it. This different instance has no effect whatsoever on the instance that you gave to the view.
What you need to do is make heightSC an instance variable. Add it to the interface declaration in the header file, then initialize it only once, and hide or modify it as needed subsequently. The key point is that you need to have a reference to the instance of the UISegmentedControl which is being drawn on the screen, a reference that lives outside the method itself that you can use the second, third, fourth, etc time you call that method.
Try using the remove segments in your button choice method pickOne. This takes it outside the showSegment method and matches the users desired action to make the change and clear off the buttons.
- (void) pickOne:(id)sender {
UISegmentedControl* userChose = sender;
if( [userChose selectedSegmentIndex] == 0 ){
your first button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 1 ){
your second button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 2 ){
your third button operation;
[heightSC removeAllSegments];
}
}
I tried this and got the results I was looking for. Thanks goes to Mythogen and BrianSlick I just need to check and make sure there are no leaks. Now that will be a task.
Does anyone know if I need the second [heightSC release]; ?
// .h
# interface ------ {
UISegmentedControl *segmentPicked;
}
|
#property (nonatomic, retain) UISegmentedControl *segmentPicked;
// .m
|
#synthesize segmentPicked;
|
if ([self segmentPicked] == nil) {
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
[self setSegmentPicked:heightSC];
[heightSC release];
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
}
if ([segmentState_height isEqualToString:#"OFF"])
{
[[self segmentPicked] setHidden:YES];
} else {
[[self segmentPicked] setHidden:NO];
}
[yourSegment removeFromSuperview];
?

UISegmentedControl selected segment color

Is there any way to customize color of selected segment in UISegmentedControl?
I've found segmentedController.tintColor property, which lets me customize color of the whole segmented control.
The problem is, when I select bright color for tintColor property, selected segment becomes almost unrecognizable (its color is almost the same as the rest of segmented control, so its hard to distinguish selected and unselected segments). So I cannot use any good bright colors for segmented control.
The solution would be some separate property for selected segment color but I cannot find it. Did anyone solve this?
Here is the absolute simplest way to change the selected segment to any RGB color. No subclassing or hacks required.
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
UIColor *newTintColor = [UIColor colorWithRed: 251/255.0 green:175/255.0 blue:93/255.0 alpha:1.0];
segmentedControl.tintColor = newTintColor;
UIColor *newSelectedTintColor = [UIColor colorWithRed: 0/255.0 green:175/255.0 blue:0/255.0 alpha:1.0];
[[[segmentedControl subviews] objectAtIndex:0] setTintColor:newSelectedTintColor];
This example shows the important steps:
Sets the control style to
"StyleBar", which is required for it
to work
Sets the un-selected color for the
entire control first to orange
Sets the color of the selected
segment to green
Notes:
Steps 1 and 2 can be done in
interface builder, or in code as
shown. However step 3 can only be done
in code
The color values being set with
notation like this "123.0/255.0" is
just a way to make the RGB values
stand out instead the normalized
float values required by UIColor
(just ignore it if you like)
I found A Simple Way to Add Color for Selected Segment in UISegmentcontrol
sender is UISegmentControl
for (int i=0; i<[sender.subviews count]; i++)
{
if ([[sender.subviews objectAtIndex:i]isSelected] )
{
UIColor *tintcolor=[UIColor colorWithRed:127.0/255.0 green:161.0/255.0 blue:183.0/255.0 alpha:1.0];
[[sender.subviews objectAtIndex:i] setTintColor:tintcolor];
}
else
{
[[sender.subviews objectAtIndex:i] setTintColor:nil];
}
}
Check its Working For Me
To do this you simply have to find the selected segment, for example by iterating over the segmented control's subviews and testing the isSelected property, then simply call the setTintColor: method on that subview.
I did this by connecting an action to each segmented control on the ValueChanged event in Interface Builder, I connected them to this this method in the view controller file which is essentially msprague's answer:
- (IBAction)segmentedControlValueChanged:(UISegmentedControl*)sender
{
for (int i=0; i<[sender.subviews count]; i++)
{
if ([[sender.subviews objectAtIndex:i] respondsToSelector:#selector(isSelected)] && [[sender.subviews objectAtIndex:i]isSelected])
{
[[sender.subviews objectAtIndex:i] setTintColor:[UIColor whiteColor]];
}
if ([[sender.subviews objectAtIndex:i] respondsToSelector:#selector(isSelected)] && ![[sender.subviews objectAtIndex:i] isSelected])
{
[[sender.subviews objectAtIndex:i] setTintColor:[UIColor blackColor]];
}
}
}
To ensure that the control is displayed correctly each time the view is opened by the user I also had to override the -(void)viewDidAppear:animated method and call the method as follows:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Ensure the segmented controls are properly highlighted
[self segmentedControlValueChanged:segmentedControlOne];
[self segmentedControlValueChanged:segmentedControlTwo];
}
For some bonus points if you do want to set the segmented control to use a white tint color on selection then you will also want to change the color of the text to black when it's selected, you can do this like so:
//Create a dictionary to hold the new text attributes
NSMutableDictionary * textAttributes = [[NSMutableDictionary alloc] init];
//Add an entry to set the text to black
[textAttributes setObject:[UIColor blackColor] forKey:UITextAttributeTextColor];
//Set the attributes on the desired control but only for the selected state
[segmentedControlOne setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
With the introduction of iOS 6 setting the tint color of the selected item for the first time in the viewDidAppear method wont work, to get around this I used grand central dispatch to change the selected color after a fraction of a second like so:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self segmentedControlValueChanged:segmentedControlOne];
});
For some reason Apple dont allow you to change the color of standard UISegmentedControls.
There is however a "legal" way around it which is to change the segmented control style to UISegmentedControlStyleBar. This makes it look slightly different which you may not like but it does allow color.
NSArray *itemArray = [NSArray arrayWithObjects: #"One", #"Two", #"Three", nil];
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:itemArray];
//Change Bar Style and ad to view then release segmented controller
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.tintColor = [UIColor colorWithRed:.9 green:.1 blue:.1 alpha:1];
[self.view addSubview:segmentedControl];
[segmentedControl release];
Hope this helped,
Seb Kade
"I'm here to help"
Edit: This solution doesn't work on iOS 6. See David Thompson's answer below.
This thread is really old, but none of the simple answers worked properly for me.
The accepted answer works as long as you revert the color of the deselected segmented controls.
Something like this will work in your value changed function:
for (int i=0; i<[control.subviews count]; i++)
{
if ([[control.subviews objectAtIndex:i]isSelected] )
{
UIColor *tintcolor=[UIColor colorWithRed:127.0/255.0 green:161.0/255.0 blue:183.0/255.0 alpha:1.0];
[[control.subviews objectAtIndex:i] setTintColor:tintcolor];
} else {
UIColor *tintcolor=[UIColor grayColor]; // default color
[[control.subviews objectAtIndex:i] setTintColor:tintcolor];
}
}
I know this is an old question But now in xcode 11 +, you can set selected segment Tint colour
In code us can use selectedSegmentTintColor. available iOS 13+
Here is my modified version of uihacker's CustomSegmentedControl (see credit in comment). The idea is I change the way to find the subview that should have the tintColor changed, from using selectedIndex to isSelected method. Because I was working with a custom UISegmentedControl that has 3 or more segments which the subview ordering changes randomly (even uihacker's "hasSetSelectedIndexOnce" flag doesn't fix this!). The code is still in early dev stage so use it at your own risk. Any comment is welcomed :)
Also, I added support to interface builder, and override setSelectedSegmentIndex so that it also updates the color. Enjoy!
CustomSegmentedControl.h
//
// CustomSegmentedControl.h
//
// Created by Hlung on 11/22/54 BE.
// Copyright (c) 2554 __MyCompanyName__. All rights reserved.
//
// Credit: http://uihacker.blogspot.com/2010/05/iphone-uisegmentedcontrol-custom-colors.html
#interface CustomSegmentedControl : UISegmentedControl {
UIColor *offColor,*onColor;
}
#property (nonatomic,retain) UIColor *offColor,*onColor;
-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor;
#end
CustomSegmentedControl.m
#import "CustomSegmentedControl.h"
#interface CustomSegmentedControl (private)
-(void)setInitialMode;
-(void)toggleHighlightColors;
#end
#implementation CustomSegmentedControl
#synthesize offColor,onColor;
-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor {
if (self = [super initWithItems:items]) {
// Initialization code
self.offColor = offcolor;
self.onColor = oncolor;
[self setInitialMode];
// default to 0, other values cause arbitrary highlighting bug
[self setSelectedSegmentIndex:0];
}
return self;
}
- (void)awakeFromNib {
// default colors
self.offColor = [UIColor colorWithWhite:0.8 alpha:1];
self.onColor = self.tintColor;
[self setInitialMode];
[self setSelectedSegmentIndex:0];
}
-(void)setInitialMode
{
// set essential properties
[self setBackgroundColor:[UIColor clearColor]];
[self setSegmentedControlStyle:UISegmentedControlStyleBar];
// loop through children and set initial tint
for( int i = 0; i < [self.subviews count]; i++ )
{
[[self.subviews objectAtIndex:i] setTintColor:nil];
[[self.subviews objectAtIndex:i] setTintColor:offColor];
}
// listen for updates, [self setSelectedSegmentIndex:0] triggers UIControlEventValueChanged in 5.0, 4.3 doesn't (facepalm), use if( self.window ) to fix this
[self addTarget:self action:#selector(toggleHighlightColors) forControlEvents:UIControlEventValueChanged];
}
// ---------------
// hlung's version
// ---------------
-(void)toggleHighlightColors
{
// the subviews array order randomly changes all the time, change to check for "isSelected" instead
for (id v in self.subviews) {
if ([v isSelected]) [v setTintColor:onColor];
else [v setTintColor:offColor];
}
}
// override: update color when set selection
- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex {
[super setSelectedSegmentIndex:selectedSegmentIndex];
[self toggleHighlightColors];
}
// ---------------
#end
Use this:
[[UISegmentedControl appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName : [UIColor colorWithRed:255.0/255 green:37.0/255 blue:99.0/255 alpha:1.0]} forState:UIControlStateSelected];
Not sure if this will get approved by the app store, but I wrote a subclass to UISegmentedControl that lets you set a custom selected and unselected color. Check the notes for more info:
http://uihacker.blogspot.com/2010/05/iphone-uisegmentedcontrol-custom-colors.html
To clarify the answer provided above by #jothikenpachi we found the following UISegmentController category worked well in iOS6 and allows for an arbitrary on/off color scheme on segments. Plus it will fail gracefully if the private methods isSelected/setTintColor: are changed in future OS releases. Caveats around private API calls, etc.
#implementation UISegmentedControl(CustomTintExtension) {
-(void) updateCustomTintColorOn:(UIColor*)onColor Off:(UIColor*)offColor {
// Convenience function to rest the tint colors after selection, called upon change of selected index
SEL tint = #selector(setTintColor:);
for (UIView *view in [self subviews]) {
// Loop through the views...
if (view && ([view respondsToSelector:tint])) {
[view performSelector:tint withObject:nil];
}
if (view && ([view respondsToSelector:tint])) {
[view performSelector:tint withObject:offColor];
}
}
// Checking if segment subview is selected...
SEL isSelected = #selector(isSelected);
for (UIView *view in [self subviews]) {
if ([view respondsToSelector:isSelected] && [view performSelector:isSelected withObject:nil])
{
[view performSelector:tint withObject:onColor];
break;
}
}
}
Note, this category method would be called from within the UISegmentController's - (IBAction) segmentAction: (id)sender method.
Also note that with iOS6 it seems you may need to call this method initially in the governing UIViewController's - (void)viewDidAppear:(BOOL)animated which may result in a animation flash. To minimize this, try setting the "offColor" as the UISegmentController's tintColor in IB.
I just ran into this issue on iOS 7, which works differently than iOS6.
In iOS 7, the color of the label for the selected segment is the same color as the UISegementControl background. The only way to change it on iOS 7 is to set the background color of the UISegmentControl.
segmentControl.backgroundColor = customColor;
I used this and it changed all the colors in one step.
mySegmentedControl.tintColor = [UIColor redColor]
I found I could use tag on the subviews with the same index as the segments, so that in any order they the segments will be colored correctly.
// In viewWillAppear set up the segmented control
// then for 3 segments:
self.navigationItem.titleView = segmentedControl;
//Order of subviews can change randomly!, so Tag them with same index as segment
[[[segmentedControl subviews]objectAtIndex:0]setTag:0];
[[[segmentedControl subviews]objectAtIndex:1]setTag:1];
[[[segmentedControl subviews]objectAtIndex:2]setTag:2];
// color follows the selected segment
- (IBAction)mySelector:(id)sender {
selector = [sender selectedSegmentIndex]
for (id seg in [segmentedControl subviews]) {
for (id label in [seg subviews]) {
if ([seg tag] == selector){
[seg setTintColor:selectedColor];
} else {
[seg setTintColor:nonSelectedColor];
}
}
}
}
// in viewDidAppear for returning to the view
[segmentedControl setSelectedSegmentIndex:selector];
for (id seg in [segmentedControl subviews]) {
for (id label in [seg subviews]) {
if ([seg tag] == selector){
[seg setTintColor:selectedColor];
} else {
[seg setTintColor:nonSelectedColor];
}
}
}
The top two solutions didn't work for me when switching between segments.
My solution was to handle the segment change event in my view controller and then call this method each time the segment is changed:
+ (void)setSegmentedControl:(UISegmentedControl *)segmentedControl
selectedColor:(UIColor *)selectedColor
deselectedColor:(UIColor *)deselectedColor
{
for (int i = 0; i < segmentedControl.subviews.count; i++)
{
id subView = [segmentedControl.subviews objectAtIndex:i];
if ([subView isSelected])
[subView setTintColor:selectedColor];
else
[subView setTintColor:deselectedColor];
}
}
I am wondering why anyone have not mentioned about UIAppearanceProxy
Apple Doc::
https://developer.apple.com/documentation/uikit/uisegmentedcontrol#1653545
Sample Code:
private class func applyUISegmentControlAppearance(){
let apperance = UISegmentedControl.appearance()
// Set Navigation bar Title colour
let unselAttrib = [NSForegroundColorAttributeName:UIColor.yellow,
NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let selAttrib = [NSForegroundColorAttributeName:UIColor.red,
NSFontAttributeName: UIFont.boldSystemFont(ofSize: 15)]
apperance.setTitleTextAttributes(unselAttrib, for: .normal)
apperance.setTitleTextAttributes(selAttrib, for: .selected)
}
Call From:
You can call this method in AppDelegate from
application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
For doing your kind of thing, one might have to access the undocumented features and hacks, which will certainly make apple furious, and that may lead to the rejection of your application.
Now, the solution lies in other trick that you use two buttons instead and have their images interchanged when they are clicked. Keep the buttons closer and images of half segmented control to give the illusion of segmented control and that is all I can suggest you.
Hope this helps.
Thanks,
Madhup
You can tag each of the segments, then set the TintColor forTag:
#define kTagOffState 0
#define kTagOnState 2
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
//usage UIColor color = UIColorFromRGB(0xF7F7F7);
UIColor onColor = UIColorFromRGB(0xF7F7F7);
UIColor offColor = UIColorFromRGB(0x878787);
[multiStateControl setTag:kTagOffState forSegmentAtIndex:0];
[multiStateControl setTag:kTagOnState forSegmentAtIndex:1];
[multiStateControl setTintColor:onColor forTag:kTagOnState];
[multiStateControl setTintColor:offColor forTag:kTagOffState];
I found the answers above very helpful. I am using the segmented control to set the precision of a knob. I took a hybrid of the answers above and came up with this:
-(void) viewDidLoad {
NSArray *segments = [NSArray arrayWithObjects:#"Course", #"Fine",nil];
[knob setPrecision:0.1]; // initial precision
// Set starting values
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segments];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.frame = CGRectMake(120, 680, 228, 30);
[segmentedControl addTarget:self action:#selector(precisionSelect:) forControlEvents:UIControlEventValueChanged];
segmentedControl.momentary = YES;
[self.view addSubview:segmentedControl];
}
- (void)precisionSelect:(UISegmentedControl*)sender
{
UIColor *tintcolor = [UIColor darkGrayColor];
if (sender.selectedSegmentIndex == 0) {
[[sender.subviews objectAtIndex:0] setTintColor:nil];
[[sender.subviews objectAtIndex:1] setTintColor:tintcolor];
[knob setPrecision:0.1]; // Coarse
} else {
[[sender.subviews objectAtIndex:0] setTintColor:tintcolor];
[[sender.subviews objectAtIndex:1] setTintColor:nil];
[knob setPrecision:0.05]; // Fine
}
}
Hope this helps others..
A key for me, was being able to reset the unselected index using: setTintColor:nil];
- (IBAction)segmentControlValueChanged:(UISegmentedControl *)sender
{
if ([[sender.subviews firstObject] respondsToSelector:#selector(setTintColor:)]) {
for (id segment in sender.subviews) {
if ([segment respondsToSelector:#selector(isSelected)] && [segment isSelected]) {
[segment setTintColor:[UIColor redColor]];
} else {
[segment setTintColor:[UIColor grayColor]];
}
}
}
}
Try this solution.
#IBAction func dashBoardSegmentValueChanged(sender: AnyObject) {
switch dashBoardSegment.selectedSegmentIndex
{
case 0:
sender.subviews.last?.backgroundColor = UIColor.whiteColor()
sender.subviews.first?.backgroundColor = UIColor.clearColor()
break;
case 1:
sender.subviews.first?.backgroundColor = UIColor.whiteColor()
sender.subviews.last?.backgroundColor = UIColor.clearColor()
break;
default:
break;
}
}
Note: Make sure you select one segment subview as initial selected for easiness. It works if you have two segment subviews.
- (IBAction)segmentedControlValueChanged:(UISegmentedControl *)sender {
for (int i = 0; i < sender.subviews.count; i++) {
UIControl *component = [sender.subviews objectAtIndex:i];
if ([component respondsToSelector:#selector(isSelected)]) {
UIColor *selectedColor = [UIColor greenColor];
UIColor *normalColor = [UIColor blackColor];
UIColor *tint = component.isSelected ? selectedColor : normalColor;
[component setTintColor:tint];
}
}
}
[segmentedControl setSelectedSegmentTintColor:[UIColor darkGrayColor]];
//For iOS 13
This Swift 4 code works for me
segmentedControl.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.red], for: .selected)