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?
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 {
backgroundColor = isHighlighted ? UIColor.black : UIColor.white
}
}
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];
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.
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
}
}
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.
isHighlighted = false
somewhere in the beginning (on inititialization for instance).
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)
}
}
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
}
}
highlighted
was a property on UIButton. The answer is that it is a property on UIControl which UIButton inherits from.
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
}
}
}
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)
}
}
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)
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
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.
You can use this category which add the method setBackgroundColor:forState:
https://github.com/damienromito/UIButton-setBackgroundColor-forState-
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)
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];
}
- (IBAction)onButtonTouchDragOutside:(UIButton *)sender {
to make sure the colour doesn't remain on when the user accidentally drags his finger off the button.
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
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.
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)
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
}
}
}
}
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.
Try this if you have an image:
-(void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state;
or see if showsTouchWhenHighlighted
is enough for you.
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).
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.
class CustomButton: UIButton {
override var isHighlighted: Bool {
didSet {
if (isHighlighted) {
alpha = 0.5
}
else {
alpha = 1
}
}
}
}
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];
Try tintColor
:
_button.tintColor = [UIColor redColor];
NSLog(@"%@", _button);
?
UIButtonTypeCustom
.
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)
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
}
}
}
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.
if you won't override just set two action touchDown touchUpInside
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)
}
}
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)
Success story sharing
backgroundColor = isHighlighted ? .lightGray : .white
isHighlighted = false
somewhere in the beginning (on inititialization for instance).