How to tint the background images of an UIButton programmatically and dynamically?

15,456

Solution 1

I made a UIImage category following another post that i cant find that takes the image and tints it as follows:

- (UIImage *)tintedImageWithColor:(UIColor *)tintColor {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
    CGContextRef context = UIGraphicsGetCurrentContext();


    CGContextTranslateCTM(context, 0, self.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

    // draw alpha-mask
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    CGContextDrawImage(context, rect, self.CGImage);

    // draw tint color, preserving alpha values of original image
    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return coloredImage;
}

This will take the image and fill in all of the areas with an alpha value with the given color.

Solution 2

In iOS7 you can use the method imagedWithRenderingMode: in conjunction with the method setTintColor:

But, be sure to use UIImageRenderingModeAlwaysTemplate as it replaces all non-transparent colors on a UIImage with the tint color. The default is UIImageRenderingModeAutomatic.

UIImageRenderingModeAlwaysTemplate

Always draw the image as a template image, ignoring its color information.

Objective-C:

UIImage * image = [myButton.currentImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[myButton setImage:image forState:UIControlStateNormal];

Swift:

let image = myButton.currentImage?.withRenderingMode(.alwaysTemplate)
myButton.setImage(image, for: .normal)

Solution 3

I created a UIButton category using horsejockey's code: https://github.com/filipstefansson/UITintedButton

Here's the new methods:

-(void)setImageTintColor:(UIColor *)color;
-(void)setBackgroundTintColor:(UIColor *)color forState:(UIControlState)state;
+(void)tintButtonImages:(NSArray *)buttons withColor:(UIColor *)color;
+(void)tintButtonBackgrounds:(NSArray *)buttons withColor:(UIColor *)color forState:(UIControlState)state;

Solution 4

Here is a partial answer to your question. This is a helper method I wrote that tints a grey scale image:

// baseImage is the grey scale image. color is the desired tint color
+ (UIImage *)tintImage:(UIImage *)baseImage withColor:(UIColor *)color {
    UIGraphicsBeginImageContextWithOptions(baseImage.size, NO, baseImage.scale);

    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect area = CGRectMake(0, 0, baseImage.size.width, baseImage.size.height);

    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -area.size.height);

    CGContextSaveGState(ctx);
    CGContextClipToMask(ctx, area, baseImage.CGImage);

    [color set];
    CGContextFillRect(ctx, area);
    CGContextRestoreGState(ctx);

    CGContextSetBlendMode(ctx, kCGBlendModeOverlay);

    CGContextDrawImage(ctx, area, baseImage.CGImage);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    // If the original image was stretchable, make the new image stretchable
    if (baseImage.leftCapWidth || baseImage.topCapHeight) {
        newImage = [newImage stretchableImageWithLeftCapWidth:baseImage.leftCapWidth topCapHeight:baseImage.topCapHeight];
    }

    return newImage;
}
Share:
15,456
Hermann Klecker
Author by

Hermann Klecker

Updated on August 02, 2022

Comments

  • Hermann Klecker
    Hermann Klecker over 1 year

    I am working on an app - or rather on some re-usable "framework" which I am happy to share once it works. Within this app the user should be able to choose from a list of color themes. Therefore the app must be able to tint its UI elements in some rather dynamic way.

    For Buttons all the tinting does not work. Properly tinted background images must be supplied here. But preparing one set of background images for each them is just second best. It is not dynamic and flexible enough.

    In the end a solution may come down to providing one monochrome (grey) gradiented image for the selected and normal state and tint that image programmatically using CoreGraphics or OpenGL. But frankly, I do not know where to start there. How should the gradient look like and how would I then programmatically tint that in any given color?

    Pretty much applies to UISegmentedControls, just a bit more complicated. :) Any generic solution that covers UISegementedControls too is highliy appreciated.

  • Hermann Klecker
    Hermann Klecker over 11 years
    It wasn't the answer I was hoping for but that seems to be doable. Thanks. I'll give it a try, probably over the weekend.
  • Hermann Klecker
    Hermann Klecker over 11 years
    Thanks so far. I'll give it a try over the weekend or early next week. It looks petty similar to the core of what @OnlyYou has linked. Although I am not quite familiar with CG Graphics jet it is quite self explaining. Just one question. This CGContextScalTCM and ContextTranslateTCM is for the coordinate transformation because UIKit and CGGraphics have different origins and a different orientation on the y-axix?
  • Alexandre G
    Alexandre G over 8 years
    Be careful - having the UIImage __weak shows no image on the button in ios 9 release mode builds
  • Raj Aggrawal
    Raj Aggrawal over 7 years
    Work for SetImage not for SetBackgroudIamge
  • Desmond DAI
    Desmond DAI about 6 years
    Work like a chram!