274

At some point in my app I have a highlighted UIButton (for example when a user has his finger on the button) and I need to change the background color while the button is highlighted (so while the finger of the user is still on the button).

I tried the following:

_button.backgroundColor = [UIColor redColor];

But it is not working. The color remains the same. I tried the same piece of code when the button is not highlighted and it works fine. I also tried calling -setNeedsDisplay after changing the color, it didn't have any effect.

How to force the button to change the background color?

Gabriel.Massana
  • 8,165
  • 6
  • 62
  • 81
MartinMoizard
  • 6,600
  • 12
  • 45
  • 75
  • Check this post https://somethingaboutios.wordpress.com/2016/02/09/uibutton-backgroundcolor-for-uicontrolstateselected/ and this pod https://github.com/GabrielMassana/ButtonBackgroundColor-iOS – Gabriel.Massana Feb 09 '16 at 21:02

32 Answers32

446

You can override UIButton's setHighlighted method.

Objective-C

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];

    if (highlighted) {
        self.backgroundColor = UIColorFromRGB(0x387038);
    } else {
        self.backgroundColor = UIColorFromRGB(0x5bb75b);
    }
}

Swift 3.0 and Swift 4.1

override open var isHighlighted: Bool {
    didSet {
        super.isHighlighted = isHighlighted
        backgroundColor = isHighlighted ? UIColor.black : UIColor.white
    }
}
Vijay Tholpadi
  • 2,135
  • 1
  • 15
  • 20
Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
  • Yeah. It is kind of nice way to do this. Because you can define several similar buttons. – Paweł Brewczynski Mar 22 '14 at 05:32
  • 5
    Just a newbie question, where would you subclass that button method? If I have a button in a view controller named ConversionViewController, how would I setup the button to change the background color when highlighted or tapped? Would I subclass the setHIghlighted in the COnversionViewController? – Beanno1116 Sep 01 '15 at 06:49
  • So there is one problem with this answer that I found. If you want to have the same color be available when you select theButton then setHighlighted gets called after setSelected and thus will override any selected styling you have. The solution above might be better if you want to select the button as well – HaloZero Sep 15 '15 at 22:51
  • Here is the problem - what if you need a few buttons of different color? Say, 1st is green when highlighted, 2nd is red and so on. You'll need to create a new class for each case. – Yakiv Kovalskyi Nov 09 '16 at 10:30
  • 3
    @YakivKovalskiy assuming you're using a sub-class, you could add two UIColor properties e.g. normalBackground and highlightedBackground, then assign self.backgroundColor = normalBackground or highlightedBackground accordingly. Don't forget to add an init method for ease of use e.g. initWithBackground:highlightedBackground: – SK. Dec 04 '16 at 18:55
  • To expand on @SK. 's addition, these properties could also be made IBInspectable so that you could do all the init through IB. – n_b Apr 05 '17 at 15:44
  • Should it be willSet instead of didSet? – Jin Wang Aug 14 '17 at 07:29
  • 2
    Nice solution, just one suggestion: `backgroundColor = isHighlighted ? .lightGray : .white` – Fantini Aug 18 '17 at 20:04
  • Works for `isSelected` as well – Fogh Aug 16 '18 at 12:34
  • 3
    Why nobody had mentioned that the setter is being called only when you tap the button, but not during the initial layout! So by default there is no colour until you touch the button. So to make it work you also need to explicitly call `isHighlighted = false` somewhere in the beginning (on inititialization for instance). – topolog Jul 01 '19 at 13:58
  • 1
    So, for Objective-C. We must subclass UIButton to achieve this effect, right? – Zhou Haibo Sep 07 '21 at 11:10
307

Not sure if this sort of solves what you're after, or fits with your general development landscape but the first thing I would try would be to change the background colour of the button on the touchDown event.

Option 1:

You would need two events to be capture, UIControlEventTouchDown would be for when the user presses the button. UIControlEventTouchUpInside and UIControlEventTouchUpOutside will be for when they release the button to return it to the normal state

UIButton *myButton =  [UIButton buttonWithType:UIButtonTypeCustom];
[myButton setFrame:CGRectMake(10.0f, 10.0f, 100.0f, 20.f)];
[myButton setBackgroundColor:[UIColor blueColor]];
[myButton setTitle:@"click me:" forState:UIControlStateNormal];
[myButton setTitle:@"changed" forState:UIControlStateHighlighted];
[myButton addTarget:self action:@selector(buttonHighlight:) forControlEvents:UIControlEventTouchDown];
[myButton addTarget:self action:@selector(buttonNormal:) forControlEvents:UIControlEventTouchUpInside];

Option 2:

Return an image made from the highlight colour you want. This could also be a category.

+ (UIImage *)imageWithColor:(UIColor *)color {
   CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
   UIGraphicsBeginImageContext(rect.size);
   CGContextRef context = UIGraphicsGetCurrentContext();

   CGContextSetFillColorWithColor(context, [color CGColor]);
   CGContextFillRect(context, rect);

   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   return image;
}

and then change the highlighted state of the button:

[myButton setBackgroundImage:[self imageWithColor:[UIColor greenColor]] forState:UIControlStateHighlighted];
Tim
  • 3,434
  • 1
  • 14
  • 13
  • 3
    Add UIControlEventTouchUpOutside and UIControlEventTouchCancel to buttonHighlight: event list and it will work always. – Evgen Bodunov Jan 22 '14 at 09:03
  • Option two is the best that I have found so far. I guess, however, that storyboards do have their advantages in this case! – Jack Solomon Apr 11 '14 at 00:27
  • Answer by Thomas is better and that's what I use also – Van Du Tran May 22 '14 at 20:17
  • 28
    If you're using `layer.cornerRadius` and go with option #2, you'll need to make sure to set `clipsToBounds` to true to get the image's corners rounded as well. – Sky Jul 24 '14 at 18:44
  • 3
    If someone stops by and needs an answer in Swift: http://stackoverflow.com/questions/26600980/how-do-i-set-uibutton-background-color-forstate-uicontrolstate-highlighted-in-s/30604658#30604658 – winterized Jun 02 '15 at 19:21
  • Put the imageWithColor method in singleton, and use it so many times in many projects. Big help. Thank you so much. – felixwcf May 26 '16 at 15:24
  • Here's a simple CocoaPod that makes images with colors, and also allows you to make resizable images with a corner radius and color: https://github.com/mxcl/UIImageWithColor – mxcl Feb 23 '17 at 22:23
  • If you do not fell like writing a new method just to generate an image you can also use [CIImage imageWithColor:] – Valentin Mercier Apr 19 '18 at 13:01
  • Just want to document my finding on iOS 13.3.1. Having `setBackgroundImage:forState` above in a `layoutSubviews` makes the system loops infinitely on `layoutSubviews` when dark mode is changed from control center. Moving it into `traitCollectionDidChange` solves the problem. – shinwan Apr 26 '21 at 05:55
100

There is no need to override highlighted as computed property. You can use property observer to trigger background color change:

override var highlighted: Bool {
    didSet {
        backgroundColor = highlighted ? UIColor.lightGrayColor() : UIColor.whiteColor()
    }
}

Swift 4

override open var isHighlighted: Bool {
    didSet {
        backgroundColor = isHighlighted ? UIColor.lightGray : UIColor.white
    }
}
Şafak Gezer
  • 3,928
  • 3
  • 47
  • 49
Aleksejs Mjaliks
  • 8,647
  • 6
  • 38
  • 44
  • 1
    I've never used functionality like this. Can you explain where this goes? Is it in the IBAction buttonPress function or in the viewDidLoad? – Dave G Apr 22 '16 at 09:33
  • What if I have multiple UIButtons with different colors? – Slavcho Aug 11 '16 at 13:15
  • 7
    @Dave G, you create a new subclass of UIButton by clicking `File>New>File>Cocoa Touch Class` and setting it to `subclass of UIButton`. Name the file for ex `CustomButton`, which will become both the file name and the class name. Inside this file, put the `override var highlighted` code shown above. Last step, set the UIButton on Interface Builder to use this `CustomButton` subclass by going to the Property page where it says "Custom Class" and has a dropdown box. It will say "UIButton" in grey letters. The dropdown list should show CustomButton. Select this, and the button is now subclassed. – James Toomey May 17 '17 at 23:44
  • 1
    Why nobody had mentioned that the setter is being called only when you tap the button, but not during the initial layout! So by default there is no colour until you touch the button. – topolog Jul 01 '19 at 11:10
  • 1
    So to make it work you also need to explicitly call `isHighlighted = false` somewhere in the beginning (on inititialization for instance). – topolog Jul 01 '19 at 11:16
55

An handy generic extension in Swift:

extension UIButton {
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        CGContextSetFillColorWithColor(context, color.CGColor)
        CGContextFillRect(context, rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
        self.setBackgroundImage(imageWithColor(color), forState: state)
    }
}

Swift 3.0

extension UIButton {
    private func imageWithColor(color: UIColor) -> UIImage? {
        let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        context?.setFillColor(color.cgColor)
        context?.fill(rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
        self.setBackgroundImage(imageWithColor(color: color), for: state)
    }
}
ViktoR
  • 691
  • 9
  • 11
Giordano Scalzo
  • 6,312
  • 3
  • 31
  • 31
48

In Swift you can override the accessor of the highlighted (or selected) property rather than overriding the setHighlighted method

override var highlighted: Bool {
        get {
            return super.highlighted
        }
        set {
            if newValue {
                backgroundColor = UIColor.blackColor()
            }
            else {
                backgroundColor = UIColor.whiteColor()
            }
            super.highlighted = newValue
        }
    }
shim
  • 9,289
  • 12
  • 69
  • 108
Jake Hall
  • 1,963
  • 22
  • 24
  • This totally works, but I'm confused how you were able to figure this out? The parameters aren't in the documentation or UIButton.h as far as I can tell. – shimizu Nov 13 '14 at 07:20
  • 1
    This is the swift syntax which emulates the behavior of overriding setHightlighted in objective c. See documentation on Computed Properties here https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html – Jake Hall Nov 13 '14 at 15:25
  • 11
    In swift you could use didSet – Dam Mar 17 '15 at 17:57
  • 1
    I have added example with property observer: http://stackoverflow.com/a/29186375/195173. – Aleksejs Mjaliks Mar 21 '15 at 18:28
  • I think what @shimizu was asking was how did you know `highlighted` was a property on UIButton. The answer is that it is a property on UIControl which UIButton inherits from. – Adam Johns May 29 '15 at 03:58
29

Override highlighted variable. Adding @IBInspectable makes you edit the highlighted backgroundColor in storyboard, which is nifty too.

class BackgroundHighlightedButton: UIButton {
    @IBInspectable var highlightedBackgroundColor :UIColor?
    @IBInspectable var nonHighlightedBackgroundColor :UIColor?
    override var highlighted :Bool {
        get {
            return super.highlighted
        }
        set {
            if newValue {
                self.backgroundColor = highlightedBackgroundColor
            }
            else {
                self.backgroundColor = nonHighlightedBackgroundColor
            }
            super.highlighted = newValue
        }
    }
}
mash
  • 4,204
  • 4
  • 32
  • 34
26

Solution for Swift 3+ without subclassing.

extension UIButton {
  func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
    let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
    UIGraphicsBeginImageContext(rect.size)
    color.setFill()
    UIRectFill(rect)
    let colorImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    setBackgroundImage(colorImage, for: state)
  }
}

With this extension it's easy to manage colors for different states and it will fade your normal color automatically in case highlighted color is not provided.

button.setBackgroundColor(.red, for: .normal)
Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58
  • 1
    Nice, this works great in Swift 5 as well. – Joshua Jun 03 '21 at 03:06
  • 2
    I really appreciate this answer because it is exactly what is missing from the API. It's analogous to the existing setTitle(for:). It should be the accepted answer IMHO. – biomiker Oct 01 '21 at 17:13
25

a more compact solution (based on @aleksejs-mjaliks answer):

Swift 3/4+:

override var isHighlighted: Bool {
    didSet {
        backgroundColor = isHighlighted ? .lightGray : .white
    }
}

Swift 2:

override var highlighted: Bool {
    didSet {
        backgroundColor = highlighted ? UIColor.lightGrayColor() : UIColor.whiteColor()
    }
}

If you don't want to override, this is an updated version of @timur-bernikowich's answer (Swift 4.2):

extension UIButton {
  func setBackgroundColor(_ color: UIColor, forState controlState: UIControl.State) {
    let colorImage = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1)).image { _ in
      color.setFill()
      UIBezierPath(rect: CGRect(x: 0, y: 0, width: 1, height: 1)).fill()
    }
    setBackgroundImage(colorImage, for: controlState)
  }
}
Federico Zanetello
  • 3,321
  • 1
  • 25
  • 22
  • @FedericoZanetello this will override isHighlighted in all the buttons in your app, which isn't a good solution in my opinion. ill go with Timur's answer. – Usama bin Attique Nov 28 '17 at 08:24
14

UIButton extension with Swift 3+ syntax:

extension UIButton {
    func setBackgroundColor(color: UIColor, forState: UIControlState) {
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
        UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
        let colorImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.setBackgroundImage(colorImage, for: forState)
    }}

Use it like:

YourButton.setBackgroundColor(color: UIColor.white, forState: .highlighted)

Original Answer: https://stackoverflow.com/a/30604658/3659227

Maverick
  • 3,209
  • 1
  • 34
  • 40
10

Here's an approach in Swift, using a UIButton extension to add an IBInspectable, called highlightedBackgroundColor. Similar to subclassing, without requiring a subclass.

private var HighlightedBackgroundColorKey = 0
private var NormalBackgroundColorKey = 0

extension UIButton {

    @IBInspectable var highlightedBackgroundColor: UIColor? {
        get {
            return objc_getAssociatedObject(self, &HighlightedBackgroundColorKey) as? UIColor
        }

        set(newValue) {
            objc_setAssociatedObject(self,
                &HighlightedBackgroundColorKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    private var normalBackgroundColor: UIColor? {
        get {
            return objc_getAssociatedObject(self, &NormalBackgroundColorKey) as? UIColor
        }

        set(newValue) {
            objc_setAssociatedObject(self,
                &NormalBackgroundColorKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    override public var backgroundColor: UIColor? {
        didSet {
            if !highlighted {
                normalBackgroundColor = backgroundColor
            }
        }
    }

    override public var highlighted: Bool {
        didSet {
            if let highlightedBackgroundColor = self.highlightedBackgroundColor {
                if highlighted {
                    backgroundColor = highlightedBackgroundColor
                } else {
                    backgroundColor = normalBackgroundColor
                }
            }
        }
    }
}

I hope this helps.

Fostah
  • 11,398
  • 10
  • 46
  • 55
9

You can use this category which add the method setBackgroundColor:forState:

https://github.com/damienromito/UIButton-setBackgroundColor-forState-

Damien Romito
  • 9,801
  • 13
  • 66
  • 84
8

Details

  • Xcode 11.1 (11A1027), Swift 5

Solution

import UIKit

extension UIColor {
    func createOnePixelImage() -> UIImage? {
        let size = CGSize(width: 1, height: 1)
        UIGraphicsBeginImageContext(size)
        defer { UIGraphicsEndImageContext() }
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        context.setFillColor(cgColor)
        context.fill(CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

extension UIButton {
    func setBackground(_ color: UIColor, for state: UIControl.State) {
        setBackgroundImage(color.createOnePixelImage(), for: state)
    }
}

Usage

button.setBackground(.green, for: .normal)
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
6
extension UIButton {
    func setBackgroundColor(color: UIColor, forState: UIControl.State) {
        let size = CGSize(width: 1, height: 1)
        UIGraphicsBeginImageContext(size)
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(color.cgColor)
        context?.fill(CGRect(origin: CGPoint.zero, size: size))
        let colorImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        setBackgroundImage(colorImage, for: forState)
    }

}

Swift 5 , thanks @Maverick

dengST30
  • 3,643
  • 24
  • 25
5

UPDATE:

Use the UIButtonBackgroundColor Swift library.

OLD:

Use the helpers below to create a 1 px x 1 px image with a grayscale fill color:

UIImage *image = ACUTilingImageGray(248/255.0, 1);

or an RGB fill color:

UIImage *image = ACUTilingImageRGB(253/255.0, 123/255.0, 43/255.0, 1);

Then, use that image to set the button's background image:

[button setBackgroundImage:image forState:UIControlStateNormal];

Helpers

#pragma mark - Helpers

UIImage *ACUTilingImageGray(CGFloat gray, CGFloat alpha)
{
    return ACUTilingImage(alpha, ^(CGContextRef context) {
        CGContextSetGrayFillColor(context, gray, alpha);
    });
}

UIImage *ACUTilingImageRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
{
    return ACUTilingImage(alpha, ^(CGContextRef context) {
        CGContextSetRGBFillColor(context, red, green, blue, alpha);
    });
}

UIImage *ACUTilingImage(CGFloat alpha, void (^setFillColor)(CGContextRef context))
{
    CGRect rect = CGRectMake(0, 0, 0.5, 0.5);
    UIGraphicsBeginImageContextWithOptions(rect.size, alpha == 1, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    setFillColor(context);
    CGContextFillRect(context, rect);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Note: ACU is the class prefix of my Cocoa Touch Static Library called Acani Utilities, where AC is for Acani, and U is for Utilities.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
5

Try this !!!!

For TouchedDown Event set One color and for TouchUpInside set the other.

- (IBAction)touchedDown:(id)sender {
    NSLog(@"Touched Down");
    btn1.backgroundColor=[UIColor redColor];
}

- (IBAction)touchUpInside:(id)sender {
    NSLog(@"TouchUpInside");
    btn1.backgroundColor=[UIColor whiteColor];    
}
Karan Alangat
  • 2,154
  • 4
  • 25
  • 56
  • 3
    Worked for me. I just had to add `- (IBAction)onButtonTouchDragOutside:(UIButton *)sender {` to make sure the colour doesn't remain on when the user accidentally drags his finger off the button. – SudoPlz Feb 03 '16 at 21:19
5

simple is that use that UIButton Extension ONLY

extension UIButton {

    func setBackgroundColor(color: UIColor, forState: UIControl.State) {
        self.clipsToBounds = true  // add this to maintain corner radius
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        if let context = UIGraphicsGetCurrentContext() {
            context.setFillColor(color.cgColor)
            context.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
            let colorImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            self.setBackgroundImage(colorImage, for: forState)
        }
    }

}

and use this

 optionButton.setBackgroundColor(color: UIColor(red:0.09, green:0.42, blue:0.82, alpha:1.0), forState: .selected)

 optionButton.setBackgroundColor(color: UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.0), forState: .highlighted)

 optionButton.setBackgroundColor(color: UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.0), forState: .normal)
Muhammad Ahmad
  • 388
  • 4
  • 9
4

Subclass the UIButton and add inspectable properties for convenient use (written in Swift 3.0):

final class SelectableBackgroundButton: UIButton {

    private struct Constants {
        static let animationDuration: NSTimeInterval = 0.1
    }

    @IBInspectable
    var animatedColorChange: Bool = true

    @IBInspectable
    var selectedBgColor: UIColor = UIColor.blackColor().colorWithAlphaComponent(0.2)

    @IBInspectable
    var normalBgColor: UIColor = UIColor.clearColor()

    override var selected: Bool {
        didSet {
            if animatedColorChange {
                UIView.animateWithDuration(Constants.animationDuration) {
                    self.backgroundColor = self.selected ? self.selectedBgColor : self.normalBgColor
                }
            } else {
                self.backgroundColor = selected ? selectedBgColor : normalBgColor
            }
        }
    }

    override var highlighted: Bool {
        didSet {
            if animatedColorChange {
                UIView.animateWithDuration(Constants.animationDuration) {
                    self.backgroundColor = self.highlighted ? self.selectedBgColor : self.normalBgColor
                }
            } else {
                self.backgroundColor = highlighted ? selectedBgColor : normalBgColor
            }
        }
    }
}
Bence Pattogato
  • 3,752
  • 22
  • 30
4

Swift 5, iOS 15+

Since iOS 15 you can use a new UIButton API to configure button appearance.

The system will automatically fade your button's background color when it is tapped. You don't need to add any additional code for it.

var configuration = UIButton.Configuration.filled()
configuration.baseBackgroundColor = .systemBlue
configuration.baseForegroundColor = .white // used for title color
configuration.title = "My button"
configuration.buttonSize = .large
let myButton = UIButton(configuration: configuration)

If you want your button to change background color on tap to some custom color, add this:

let configHandler: UIButton.ConfigurationUpdateHandler = { button in
    switch button.state {
    case .highlighted:
        configuration.baseBackgroundColor = .systemRed
    default:
        configuration.baseBackgroundColor = .systemBlue
    }
}
myButton.configurationUpdateHandler = configHandler

There is a great article on topic: https://sarunw.com/posts/dynamic-button-configuration/

Apple docs: https://developer.apple.com/documentation/uikit/uibutton/configuration

Ilya Biltuev
  • 166
  • 1
  • 7
  • Please [don't post identical answers to multiple questions](https://meta.stackexchange.com/q/104227). Instead, tailor the answer to the question asked. If the questions are exact duplicates of each other, please vote/flag to close instead. – Samuel Liew Aug 03 '23 at 07:14
  • @SamuelLiew I removed the duplicate answer. Thank you! – Ilya Biltuev Aug 11 '23 at 11:15
3

You can subclass the UIButton and make a nice forState.

colourButton.h

#import <UIKit/UIKit.h>

@interface colourButton : UIButton

-(void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state;

@end

colourButton.m

#import "colourButton.h"

@implementation colourButton
{
    NSMutableDictionary *colours;
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];

    // If colours does not exist
    if(!colours)
    {
        colours = [NSMutableDictionary new];  // The dictionary is used to store the colour, the key is a text version of the ENUM
        colours[[NSString stringWithFormat:@"%lu", UIControlStateNormal]] = (UIColor*)self.backgroundColor;  // Store the original background colour
    }

    return self;
}

-(void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state
{
    // If it is normal then set the standard background here
    if(state & UIControlStateNormal)
    {
        [super setBackgroundColor:backgroundColor];
    }

    // Store the background colour for that state
    colours[[NSString stringWithFormat:@"%lu", state]]= backgroundColor;
}

-(void)setHighlighted:(BOOL)highlighted
{
    // Do original Highlight
    [super setHighlighted:highlighted];

    // Highlight with new colour OR replace with orignial
    if (highlighted && colours[[NSString stringWithFormat:@"%lu", UIControlStateHighlighted]])
    {
        self.backgroundColor = colours[[NSString stringWithFormat:@"%lu", UIControlStateHighlighted]];
    }
    else
    {
        self.backgroundColor = colours[[NSString stringWithFormat:@"%lu", UIControlStateNormal]];
    }
}

-(void)setSelected:(BOOL)selected
{
    // Do original Selected
    [super setSelected:selected];

    // Select with new colour OR replace with orignial
    if (selected && colours[[NSString stringWithFormat:@"%lu", UIControlStateSelected]])
    {
        self.backgroundColor = colours[[NSString stringWithFormat:@"%lu", UIControlStateSelected]];
    }
    else
    {
        self.backgroundColor = colours[[NSString stringWithFormat:@"%lu", UIControlStateNormal]];
    }
}

@end

Notes (This is an example, I know there are problems and here are some)

I have used an NSMutableDictionay to store the UIColor for each State, I have to do a nasty text conversion for the Key as the UIControlState is not a nice straight Int. If it where you could init an Array with that many objects and use the State as an index.

Because of this you many have difficulties with e.g. a selected & disabled button, some more logic is needed.

Another problem is if you try and set multiple colours at the same time, I have not tried with a button but if you can do this it may not work

 [btn setBackgroundColor:colour forState:UIControlStateSelected & UIControlStateHighlighted];

I have assumed this is StoryBoard, there is no init, initWithFrame so add them if you need them.

Recycled Steel
  • 2,272
  • 3
  • 30
  • 35
2

Try this if you have an image:

-(void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state;

or see if showsTouchWhenHighlighted is enough for you.

Pang
  • 9,564
  • 146
  • 81
  • 122
Siby
  • 318
  • 1
  • 10
  • I tried playing around with showsTouchWhenHighlighted but it didn't help. I don't want to use setBackgroundImage:forState:. I was in fact trying to use the backgroundColor to not use any image. – MartinMoizard Jan 25 '13 at 14:31
2

I have open-sourced a UIButton subclass, STAButton, to fill in this gaping functionality hole. Available under the MIT license. Works for iOS 7+ (I have not tested with older iOS versions).

Stunner
  • 12,025
  • 12
  • 86
  • 145
2

To solve this problem I created a Category to handle backgroundColor States with UIButtons:
ButtonBackgroundColor-iOS

You can install the category as a pod.

Easy to use with Objective-C

@property (nonatomic, strong) UIButton *myButton;

...

[self.myButton bbc_backgroundColorNormal:[UIColor redColor]
                 backgroundColorSelected:[UIColor blueColor]];

Even more easy to use with Swift:

import ButtonBackgroundColor

...

let myButton:UIButton = UIButton(type:.Custom)

myButton.bbc_backgroundColorNormal(UIColor.redColor(), backgroundColorSelected: UIColor.blueColor())

I recommend you import the pod with:

platform :ios, '8.0'
use_frameworks!

pod 'ButtonBackgroundColor', '~> 1.0'

Using use_frameworks! in your Podfile makes easier to use your pods with Swift and objective-C.

IMPORTANT

I also wrote a Blog Post with more information.

Gabriel.Massana
  • 8,165
  • 6
  • 62
  • 81
2
class CustomButton: UIButton {

    override var isHighlighted: Bool {
        didSet {
            if (isHighlighted) {
                alpha = 0.5
            }
            else {
                alpha = 1
            }            
        }
    }

}
evya
  • 3,381
  • 1
  • 25
  • 28
2

Use https://github.com/swordray/UIButtonSetBackgroundColorForState

Add to Podfile using CocoaPods

pod "UIButtonSetBackgroundColorForState"

Swift

button.setBackgroundColor(.red, forState: .highlighted)

Objective-C

[button setBackgroundColor:[UIColor redColor] forState:UIControlStateHighlighted];
swordray
  • 833
  • 9
  • 21
2

You can easily change the highlighted/selected button background color by simply using the setBackgroundImage method on UIButton and using an image by using this UIImage(color:) initializer, like this:

btn.setBackgroundImage(UIImage(color: .black), for: .highlighted)

Note:

If you use the cornerRadius property for rounded borders you have to set the clipsToBounds to true so the selected background color will reserve the corner radius value.

  • 1
    What version of iOS/Swift does this apply to? In iOS15/Swift 5 there doesn't appear to be a UIImage constructor that takes a "color". –  Mar 16 '22 at 13:57
1

Try tintColor:

_button.tintColor = [UIColor redColor];
jjv360
  • 4,120
  • 3
  • 23
  • 37
1

Here is the code in Swift to select for button state:

func imageWithColor(color:UIColor) -> UIImage {
    let rect:CGRect = CGRectMake(0.0, 0.0, 1.0, 1.0)
     UIGraphicsBeginImageContext(rect.size)
    let context:CGContextRef = UIGraphicsGetCurrentContext()!
    CGContextSetFillColorWithColor(context, color.CGColor)
    CGContextFillRect(context, rect)
    let image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
    return image;
}

Example:

    self.button.setImage(self.imageWithColor(UIColor.blackColor()), forState: .Highlighted)
Mike Zriel
  • 1,575
  • 1
  • 17
  • 28
1

Drop it in and you're good to go:
*proerty can be set in IB, and if no highlighted background is set, background will not change when pressed

private var highlightedBackgroundColors = [UIButton:UIColor]()
private var unhighlightedBackgroundColors = [UIButton:UIColor]()
extension UIButton {

    @IBInspectable var highlightedBackgroundColor: UIColor? {
        get {
            return highlightedBackgroundColors[self]
        }

        set {
            highlightedBackgroundColors[self] = newValue
        }
    }

    override open var backgroundColor: UIColor? {
        get {
            return super.backgroundColor
        }

        set {
            unhighlightedBackgroundColors[self] = newValue
            super.backgroundColor = newValue
        }
    }

    override open var isHighlighted: Bool {
        get {
            return super.isHighlighted
        }

        set {
            if highlightedBackgroundColor != nil {
                super.backgroundColor = newValue ? highlightedBackgroundColor : unhighlightedBackgroundColors[self]
            }
            super.isHighlighted = newValue
        }
    }
}
shoe
  • 952
  • 1
  • 20
  • 44
0

if you won't override just set two action touchDown touchUpInside

Roman Bambura
  • 177
  • 1
  • 6
0

Below UIIImage extension will generates image object with specified color parameter.

extension UIImage {
    static func imageWithColor(tintColor: UIColor) -> UIImage {
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
        tintColor.setFill()
        UIRectFill(rect)
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
       }
    }

An example usage for a button can be applied for the button object as :

setupButton.setBackgroundImage(UIImage.imageWithColor(tintColor: UIColor(displayP3Red: 232/255, green: 130/255, blue: 121/255, alpha: 1.0)), for: UIControlState.highlighted)

setupButton.setBackgroundImage(UIImage.imageWithColor(tintColor: UIColor(displayP3Red: 255/255, green: 194/255, blue: 190/255, alpha: 1.0)), for: UIControlState.normal)
byJeevan
  • 3,728
  • 3
  • 37
  • 60
0

Swift 3:

extension UIButton {
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRect(x:0.0,y:0.0,width: 1.0,height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        context!.setFillColor(color.cgColor)
        context!.fill(rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image!
    }

    func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
        self.setBackgroundImage(imageWithColor(color: color), for: state)
    }
}
Elita
  • 77
  • 10
0

in Swift 5

For those who don't want to use colored background to beat the selected state

Simply you can beat the problem by using #Selector & if statement to change the UIButton colors for each state individually easily

For Example:

    override func viewDidLoad() {
    super.viewDidLoad()
    self.myButtonOutlet.backgroundColor = UIColor.white  //to reset the button color to its original color ( optionally )
}

@IBOutlet weak var myButtonOutlet: UIButton!{
    didSet{  // Button selector and image here
        self.myButtonOutlet.setImage(UIImage(systemName: ""), for: UIControl.State.normal)

        self.myButtonOutlet.setImage(UIImage(systemName: "checkmark"), for: UIControl.State.selected)



        self.myButtonOutlet.addTarget(self, action: #selector(tappedButton), for: UIControl.Event.touchUpInside)
    }
}

@objc func tappedButton() {  // Colors selection is here
    if self.myButtonOutlet.isSelected == true {

        self.myButtonOutlet.isSelected = false
        self.myButtonOutlet.backgroundColor = UIColor.white         
    } else {
        self.myButtonOutlet.isSelected = true

        self.myButtonOutlet.backgroundColor = UIColor.black
        self.myButtonOutlet.tintColor00 = UIColor.white

    }
}
Let.Simoo
  • 93
  • 9