David Yeiser
David Yeiser

Reputation: 1568

Title color of UIButton won't change on highlighted/selected but background color will

This has been a very odd process.

I have an IBOutletCollection of UIButtons. I loop through the collection and create them like this (the displayHourButtons is called from viewWillAppear):

- (void)displayHourButtons
{
    // Counter
    NSUInteger b = 0;

    // Set attributes
    UIFont *btnFont = [UIFont fontWithName:@"Metric-Semibold" size:13.0];
    UIColor *btnTextColor = [UIColor colorWithRed:(147/255.0f) green:(147/255.0f) blue:(147/255.0f) alpha:1.0];
    NSNumber *btnTracking = [NSNumber numberWithFloat:0.25];
    NSMutableParagraphStyle *btnStyle = [[NSMutableParagraphStyle alloc] init];
    [btnStyle setLineSpacing:2.0];

    NSDictionary *btnAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                              btnFont, NSFontAttributeName,
                              btnTextColor, NSForegroundColorAttributeName,
                              btnTracking, NSKernAttributeName, nil];

    // CREATE THE BUTTONS
    for (UIButton *hourButton in hourButtons) {
            // I'm using the attributed string for something else
            // later in development that I haven't got to yet. 
            // I simplified the string for this example's sake.
        NSString *btnTitleText = [NSString stringWithFormat:@"Button %lu", (unsigned long)b];

        NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc]
                                                     initWithString:btnTitleText
                                                     attributes:btnAttrs];

        [attributedText addAttribute:NSParagraphStyleAttributeName
                               value:btnStyle
                               range:NSMakeRange(0, btnTitleText.length)];


        CALayer *btnLayer = [hourButton layer];
        [btnLayer setMasksToBounds:YES];
        [btnLayer setCornerRadius:19.0f];
        [hourButton setTag:b];
        [hourButton setContentEdgeInsets:UIEdgeInsetsMake(5.0, 1.0, 0.0, 0.0)];
        [hourButton setAttributedTitle:attributedText forState:UIControlStateNormal];
        [hourButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter];
        [hourButton setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
        hourButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
        [hourButton addTarget:self action:@selector(showHour:) forControlEvents:UIControlEventTouchUpInside]; 

        b++;
    }
}

When one of the buttons is clicked, per the action showHour: is called:

- (IBAction)showHour:(id)sender
{
    [self.hourButtons enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        UIButton *button = (UIButton *)obj;

        if (button != sender && button.enabled) {
                // This is applied. I know because I tested it with redColor
            [button setBackgroundColor:[UIColor clearColor]];

            // Doesn't change, stays the gray set initially
            [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        }
        else {
                // This is applied
            [button setBackgroundColor:[UIColor colorWithRed:(169/255.0f) green:(234/255.0f) blue:(255/255.0f) alpha:1.0]];

            // This is not
            [button setTitleColor:[UIColor whiteColor] forState:(UIControlStateNormal | UIControlStateSelected | UIControlStateHighlighted)];
        }
    }];

    // displayHour uses the tag to change labels, images, etc.
    [self displayHour:(long int)[sender tag]];
}

I tried all sorts of crazy things to get the UIImage to be in a selected state, but nothing worked. This enumerateObjects deal is the only thing that has worked. That's why I say this has been an odd process. I guess buttons don't stay active indefinitely?

Anyways, MY QUESTION: Is there a certain reason why the title color isn't changing? Just the background? I suspect it has something to do with the background not being set initially, but I couldn't explain why.

Thanks!

UPDATED

Per @Timothy Moose's answer, below is the updated code.

- (IBAction)showHour:(id)sender
{   
    [self.hourButtons enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        UIButton *button = (UIButton *)obj;

        // Grab the mutable string from the button and make a mutable copy
        NSMutableAttributedString *attributedText = [[button attributedTitleForState:UIControlStateNormal] mutableCopy];

        // Shared attribute styles
        UIFont *btnFont = [UIFont fontWithName:@"Metric-Semibold" size:14.0];
        NSNumber *btnTracking = [NSNumber numberWithFloat:0.25];
        NSMutableParagraphStyle *btnStyle = [[NSMutableParagraphStyle alloc] init];
        [btnStyle setLineSpacing:2.0];

        // Since we can't set a color directly on a Attributed string we have
        // to make a new attributed string.
        if (button != sender && button.enabled) {
            // Return to the default color
            UIColor *btnTextColor = [UIColor colorWithRed:(147/255.0f) green:(147/255.0f) blue:(147/255.0f) alpha:1.0];

            // Set up attributes
            NSDictionary *btnAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                      btnFont, NSFontAttributeName,
                                      btnTextColor, NSForegroundColorAttributeName,
                                      btnTracking, NSKernAttributeName, nil];

            // Reapply the default color (for the one button that was changed to white)
            [attributedText setAttributes:btnAttrs
                                    range:NSMakeRange(0, attributedText.length)];

            // Add line-height
            [attributedText addAttribute:NSParagraphStyleAttributeName
                                   value:btnStyle
                                   range:NSMakeRange(0, attributedText.length)];

            // Reset default attributes
            [button setBackgroundColor:[UIColor clearColor]];
            [button setAttributedTitle:attributedText forState:UIControlStateNormal];
        }
        else {
            // Our new white color for the active button
            UIColor *btnTextColor = [UIColor whiteColor];

            // Set up attributes
            NSDictionary *btnAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                      btnFont, NSFontAttributeName,
                                      btnTextColor, NSForegroundColorAttributeName,
                                      btnTracking, NSKernAttributeName, nil];

            // Apply our new white color
            [attributedText setAttributes:btnAttrs
                                    range:NSMakeRange(0, attributedText.length)];

            // Add line-height
            [attributedText addAttribute:NSParagraphStyleAttributeName
                                   value:btnStyle
                                   range:NSMakeRange(0, attributedText.length)];

            // Add new attributes for active button
            [button setBackgroundColor:[UIColor colorWithRed:(169/255.0f) green:(234/255.0f) blue:(255/255.0f) alpha:1.0]];
            [button setAttributedTitle:attributedText forState:UIControlStateNormal];
        }
    }];

    [self displayHour:(long int)[sender tag]];
}

Upvotes: 11

Views: 14318

Answers (6)

ttarik
ttarik

Reputation: 3853

An alternative to the above answers is to apply the text colour using string attributes. You can set a different NSAttributedString for each control state, so this works to achieve the same effect - the button text will change colour on select/highlight.

Example:

// We're assuming attributedString already exists - this is your completed attributed string so far
// We're going to copy this string into two more NS(Mutable)AttributedString variables - one for the "normal" state and one for the "highlighted" state
NSMutableAttributedString *normalAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString];
// Set the desired foreground color (in this case it's for the "normal" state) for the length of the string
[normalAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0,attributedString.length)];

// Rinse and repeat for the highlighted state
NSMutableAttributedString *highlightedAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString];
[highlightedAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0,attributedString.length)];

// Finally, we'll set these as the attributedTitles for the relevant control states.
[myButton setAttributedTitle:normalAttributedString forState:UIControlStateNormal];
[myButton setAttributedTitle:highlightedAttributedString forState:UIControlStateSelected];
[myButton setAttributedTitle:highlightedAttributedString forState:UIControlStateHighlighted];

Upvotes: 0

In my case, I am using XCode 7.x.

I faced up with the similar problem. After using NSAttributedString

let underlineAttribute = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue]
        let underlineAttributedString = NSAttributedString(string: "FILTER", attributes: underlineAttribute)
        filterButton.setTitleColor(AppConfig.FOREGROUND, forState: .Normal)
        filterButton.setAttributedTitle(underlineAttributedString, forState: .Normal)

the filterButton.setTitleColor(AppConfig.FOREGROUND, forState: .Normal) not get effected.

I change the Tint Color of the button in the Interface Builder (which is default as light blue). Now, it works for me now.

Upvotes: 0

Alex Cio
Alex Cio

Reputation: 6052

I created a custom class MyButton extended from UIButton. Then added this inside the Identity Inspector:

enter image description here

After this, change the button type to Custom:

enter image description here

Then you can set attributes like textColor and UIFont for your UIButton for the different states:

enter image description here

Then I also created two methods inside MyButton class which I have to call inside my code when I want a UIButton to be displayed as highlighted:

- (void)changeColorAsUnselection{
    [self setTitleColor:[UIColor colorFromHexString:acColorGreyDark] 
               forState:UIControlStateNormal & 
                        UIControlStateSelected & 
                        UIControlStateHighlighted];
}

- (void)changeColorAsSelection{
    [self setTitleColor:[UIColor colorFromHexString:acColorYellow] 
               forState:UIControlStateNormal & 
                        UIControlStateHighlighted & 
                        UIControlStateSelected];
}

You have to set the titleColor for normal, highlight and selected UIControlState because there can be more than one state at a time according to the documentation of UIControlState. If you don't create these methods, the UIButton will display selection or highlighting but they won't stay in the UIColor you setup inside the UIInterface Builder because they are just available for a short display of a selection, not for displaying selection itself.

Upvotes: 3

user2277872
user2277872

Reputation: 2973

I noticed this immediately. Just a simple error probably :)

Change

UIColor *btnTextColor = [UIColor colorWithRed:(147/255.f) 
                                        green:(147/255.f) 
                                         blue:(147/255.f) alpha:1.0];

to

UIColor *btnTextColor = [UIColor colorWithRed:(147/255.0f) 
                                        green:(147/255.0f) 
                                         blue:(147/255.0f) alpha:1.0];

The reason it wasn't changing is probably because it wasn't recognizing the UIColor since you didn't have a full number in the division, since it was seeing (147/255.) instead of (147/255.0)

Upvotes: -2

Catalin
Catalin

Reputation: 1899

Also it is very important not to have the button on System style, just put it on custom... This is for similar questions, not for this particular question.

Upvotes: 20

Timothy Moose
Timothy Moose

Reputation: 9915

setTitleColor doesn't have any effect when the title is an attributed string. Either use a plain NSString or call setAttributedTitle again after applying the desired color to the attributed string.

Upvotes: 20

Related Questions