36

hey, I want to be able to have a gradient fill on the text in a UILabel I know about CGGradient but i dont know how i would use it on a UILabel's text

i found this on google but i cant manage to get it to work

http://silverity.livejournal.com/26436.html

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
DotSlashSlash
  • 5,143
  • 5
  • 30
  • 32

11 Answers11

117

I was looking for a solution and DotSlashSlash has the answer hidden in one of the comments!

For the sake of completeness, the answer and the simplest solution is:

UIImage *myGradient = [UIImage imageNamed:@"textGradient.png"];
myLabel.textColor   = [UIColor colorWithPatternImage:myGradient];
Bach
  • 2,684
  • 5
  • 26
  • 36
  • This works very well! Much cleaner than the core graphics solution! – Boris Oks Dec 13 '12 at 20:37
  • great answer. be careful to use `colorWithPatternImage` sparingly though, as it can be a real memory hog. – Liam Mar 08 '13 at 09:33
  • Yep this took moments to implement, I agree tick this! – Recycled Steel Jun 13 '13 at 08:34
  • @Bach If used on a sized-to-fit UIlabel is it possible to resize the pattern image accordingly? – luca Oct 01 '13 at 07:41
  • @luca the image won't be scaled, only repeated in x and y. So if you are using a gradient, make sure the image height is exactly the height of your label, then you can have a flexible width. – Bach Oct 04 '13 at 07:16
  • This is nice except that it doesn't allow masking a gradient on an existing color... – jjxtra Apr 06 '14 at 05:41
  • @PsychoDad what do you mean? – Bach May 14 '14 at 13:33
  • This works, but of course the only draw back to this approach is you must already have a PNG image with the colors you want to apply as the gradient. If you wanted to determine the colors for the gradient at run time, for maybe a slick shadow or blur, then you would need to go with the approach of @Dimitris's answer or better yet my solution. – Brandon A Sep 13 '17 at 20:12
  • Saw this and had to try it out. Works perfectly. I did have to play with the Graident Image size to get a good fit but this is awesome. – Mick Jun 21 '18 at 18:41
43

(Skip to bottom for full class source code)

Really useful answers by both Brad Larson and Bach. The second worked for me but it requires an image to be present in advance. I wanted something more dynamic so I combined both solutions into one:

  • draw the desired gradient on a UIImage
  • use the UIImage to set the color pattern

The result works and in the screenshot below you can see some Greek characters rendered fine too. (I have also added a stroke and a shadow on top of the gradient)

iOS stylized UILabel, the big brown fox

Here's the custom init method of my label along with the method that renders a gradient on a UIImage (part of the code for that functionality I got from a blog post I can not find now to reference it):

- (id)initWithFrame:(CGRect)frame text:(NSString *)aText {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.text = aText;

        self.textColor = [UIColor colorWithPatternImage:[self gradientImage]];

    }
    return self;
}

- (UIImage *)gradientImage
{
    CGSize textSize = [self.text sizeWithFont:self.font];
    CGFloat width = textSize.width;         // max 1024 due to Core Graphics limitations
    CGFloat height = textSize.height;       // max 1024 due to Core Graphics limitations

    // create a new bitmap image context
    UIGraphicsBeginImageContext(CGSizeMake(width, height));

    // get context
    CGContextRef context = UIGraphicsGetCurrentContext();       

    // push context to make it current (need to do this manually because we are not drawing in a UIView)
    UIGraphicsPushContext(context);                             

    //draw gradient    
    CGGradientRef glossGradient;
    CGColorSpaceRef rgbColorspace;
    size_t num_locations = 2;
    CGFloat locations[2] = { 0.0, 1.0 };
    CGFloat components[8] = { 0.0, 1.0, 1.0, 1.0,  // Start color
                            1.0, 1.0, 0.0, 1.0 }; // End color
    rgbColorspace = CGColorSpaceCreateDeviceRGB();
    glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
    CGPoint topCenter = CGPointMake(0, 0);
    CGPoint bottomCenter = CGPointMake(0, textSize.height);
    CGContextDrawLinearGradient(context, glossGradient, topCenter, bottomCenter, 0);

    CGGradientRelease(glossGradient);
    CGColorSpaceRelease(rgbColorspace); 

    // pop context 
    UIGraphicsPopContext();                             

    // get a UIImage from the image context
    UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();

    // clean up drawing environment
    UIGraphicsEndImageContext();

    return  gradientImage;
}

I'll try to complete that UILabel subclass and post it.

EDIT:

The class is done and it's on my GitHub repository. Read about it here!

Dimitris
  • 13,480
  • 17
  • 74
  • 94
26

Swift 4.1

class GradientLabel: UILabel {
    var gradientColors: [CGColor] = []

    override func drawText(in rect: CGRect) {
        if let gradientColor = drawGradientColor(in: rect, colors: gradientColors) {
            self.textColor = gradientColor
        }
        super.drawText(in: rect)
    }

    private func drawGradientColor(in rect: CGRect, colors: [CGColor]) -> UIColor? {
        let currentContext = UIGraphicsGetCurrentContext()
        currentContext?.saveGState()
        defer { currentContext?.restoreGState() }

        let size = rect.size
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
                                        colors: colors as CFArray,
                                        locations: nil) else { return nil }

        let context = UIGraphicsGetCurrentContext()
        context?.drawLinearGradient(gradient,
                                    start: CGPoint.zero,
                                    end: CGPoint(x: size.width, y: 0),
                                    options: [])
        let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        guard let image = gradientImage else { return nil }
        return UIColor(patternImage: image)
    }
}

Usage:

label.gradientColors = [UIColor.blue.cgColor, UIColor.red.cgColor]
Binh Le
  • 486
  • 5
  • 12
21

SWIFT 3+

This solution is based on @Dimitris's answer. It is an extension on the UILabel class that will create a gradient over the label's text per your passed startColor and endColor. The UILabel extension is below:

extension UILabel {

    func applyGradientWith(startColor: UIColor, endColor: UIColor) -> Bool {

        var startColorRed:CGFloat = 0
        var startColorGreen:CGFloat = 0
        var startColorBlue:CGFloat = 0
        var startAlpha:CGFloat = 0

        if !startColor.getRed(&startColorRed, green: &startColorGreen, blue: &startColorBlue, alpha: &startAlpha) {
            return false
        }

        var endColorRed:CGFloat = 0
        var endColorGreen:CGFloat = 0
        var endColorBlue:CGFloat = 0
        var endAlpha:CGFloat = 0

        if !endColor.getRed(&endColorRed, green: &endColorGreen, blue: &endColorBlue, alpha: &endAlpha) {
            return false
        }

        let gradientText = self.text ?? ""

        let name:String = NSFontAttributeName
        let textSize: CGSize = gradientText.size(attributes: [name:self.font])
        let width:CGFloat = textSize.width
        let height:CGFloat = textSize.height

        UIGraphicsBeginImageContext(CGSize(width: width, height: height))

        guard let context = UIGraphicsGetCurrentContext() else {
            UIGraphicsEndImageContext()
            return false
        }

        UIGraphicsPushContext(context)

        let glossGradient:CGGradient?
        let rgbColorspace:CGColorSpace?
        let num_locations:size_t = 2
        let locations:[CGFloat] = [ 0.0, 1.0 ]
        let components:[CGFloat] = [startColorRed, startColorGreen, startColorBlue, startAlpha, endColorRed, endColorGreen, endColorBlue, endAlpha]
        rgbColorspace = CGColorSpaceCreateDeviceRGB()
        glossGradient = CGGradient(colorSpace: rgbColorspace!, colorComponents: components, locations: locations, count: num_locations)
        let topCenter = CGPoint.zero
        let bottomCenter = CGPoint(x: 0, y: textSize.height)
        context.drawLinearGradient(glossGradient!, start: topCenter, end: bottomCenter, options: CGGradientDrawingOptions.drawsBeforeStartLocation)

        UIGraphicsPopContext()

        guard let gradientImage = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            return false
        }

        UIGraphicsEndImageContext()

        self.textColor = UIColor(patternImage: gradientImage)

        return true
    }

}

And usage:

let text = "YAAASSSSS!"
label.text = text
if label.applyGradientWith(startColor: .red, endColor: .blue) {
    print("Gradient applied!")
}
else {
    print("Could not apply gradient")
    label.textColor = .black
}

YAAASSSSS!


SWIFT 2

class func getGradientForText(text: NSString) -> UIImage {

    let font:UIFont = UIFont(name: "YourFontName", size: 50.0)!
    let name:String = NSFontAttributeName
    let textSize: CGSize = text.sizeWithAttributes([name:font])
    let width:CGFloat = textSize.width         // max 1024 due to Core Graphics limitations
    let height:CGFloat = textSize.height       // max 1024 due to Core Graphics limitations

    //create a new bitmap image context
    UIGraphicsBeginImageContext(CGSizeMake(width, height))

    // get context
    let context = UIGraphicsGetCurrentContext()

    // push context to make it current (need to do this manually because we are not drawing in a UIView)
    UIGraphicsPushContext(context!)

    //draw gradient
    let glossGradient:CGGradientRef?
    let rgbColorspace:CGColorSpaceRef?
    let num_locations:size_t = 2
    let locations:[CGFloat] = [ 0.0, 1.0 ]
    let components:[CGFloat] = [(202 / 255.0), (197 / 255.0), (52 / 255.0), 1.0,  // Start color
                                (253 / 255.0), (248 / 255.0), (101 / 255.0), 1.0] // End color
    rgbColorspace = CGColorSpaceCreateDeviceRGB();
    glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
    let topCenter = CGPointMake(0, 0);
    let bottomCenter = CGPointMake(0, textSize.height);
    CGContextDrawLinearGradient(context, glossGradient, topCenter, bottomCenter, CGGradientDrawingOptions.DrawsBeforeStartLocation);

    // pop context
    UIGraphicsPopContext();

    // get a UIImage from the image context
    let gradientImage = UIGraphicsGetImageFromCurrentImageContext();

    // clean up drawing environment
    UIGraphicsEndImageContext();

    return  gradientImage;
}

Props to @Dimitris

Brandon A
  • 8,153
  • 3
  • 42
  • 77
14

The example you provide relies on private text drawing functions that you don't have access to on the iPhone. The author provides an example of how to do this using public API in a subsequent post. His later example uses a gradient image for the color of the text. (Unfortunately, it appears his blog has since been removed, but see Bach's answer here for the approach he used.)

If you still want to draw the gradient for your text color in code, it can be done by subclassing UILabel and overriding -drawRect: to have code like the following within it:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);

CGContextSelectFont(context, "Helvetica", 20.0f, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextClip);
CGContextSetTextPosition(context, 0.0f, round(20.0f / 4.0f));
CGContextShowText(context, [self.text UTF8String], strlen([self.text UTF8String]));

CGContextClip(context);

CGGradientRef gradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0,  // Start color
    1.0, 1.0, 1.0, 0.1 }; // End color

rgbColorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);

CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(context, gradient, topCenter, midCenter, 0);

CGGradientRelease(gradient);
CGColorSpaceRelease(rgbColorspace);         

CGContextRestoreGState(context);

One shortcoming of this approach is that the Core Graphics functions I use don't handle Unicode text properly.

What the code does is it flips the drawing context vertically (the iPhone inverts the normal Quartz coordinate system on for the Y axis), sets the text drawing mode to intersect the drawn text with the clipping path, clips the area to draw to the text, and then draws a gradient. The gradient will only fill the text, not the background.

I tried using NSString's -drawAtPoint: method for this, which does support Unicode, but all the characters ran on top of one another when I switched the text mode to kCGTextClip.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • 11
    thanks for that :) i simiply just used the pattern image method label.textColor = [UIColor colorWithPatternImage:gradientImage; – DotSlashSlash Aug 12 '09 at 20:37
  • @PsychoDad - Unfortunately, it looks like the entire blog of his has been removed, but the key information from it is captured in Bach's answer, and the rest of the answer here still stands. Dimitris even has expanded on this in his answer. Thanks for pointing it out. – Brad Larson Aug 18 '12 at 13:24
7

There is a really simple solution for this! Here's how you add gradient colors to UILabel text.

We will achieve this in just two steps:

  1. Create Gradient Image
  2. Apply Gradient Image As textColor to UILabel

1.Create Gradient Image

extension UIImage {
    static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = bounds
        gradientLayer.colors = colors

        UIGraphicsBeginImageContext(gradientLayer.bounds.size)
        gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }
}

Use this as follows:

let gradientImage = UIImage.gradientImageWithBounds(bounds: myLabel.bounds, colors: [firstColor.cgColor, secondColor.cgColor])

2.Apply Gradient Image As textColor to UILabel

myLabel.textColor = UIColor.init(patternImage: gradientImage)

Note:

If you want the gradient to be horizontal, just add these two lines to gradientLayer instance:

gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)

Note 2:

The UIImage extension function works with other UIViews too; not just UILabel! So feel free to use this method no matter which UIView you use to apply gradient color.

Eray Alparslan
  • 806
  • 7
  • 12
5

Here's what I'm doing in Swift 3

override func viewDidLoad() {
    super.viewDidLoad()
    timerLabel.textColor = UIColor(patternImage: gradientImage(size: timerLabel.frame.size, color1: CIColor(color: UIColor.green), color2: CIColor(color: UIColor.red), direction: .Left))
}

func gradientImage(size: CGSize, color1: CIColor, color2: CIColor, direction: GradientDirection = .Up) -> UIImage {

    let context = CIContext(options: nil)
    let filter = CIFilter(name: "CILinearGradient")
    var startVector: CIVector
    var endVector: CIVector

    filter!.setDefaults()

    switch direction {
    case .Up:
        startVector = CIVector(x: size.width * 0.5, y: 0)
        endVector = CIVector(x: size.width * 0.5, y: size.height)
    case .Left:
        startVector = CIVector(x: size.width, y: size.height * 0.5)
        endVector = CIVector(x: 0, y: size.height * 0.5)
    case .UpLeft:
        startVector = CIVector(x: size.width, y: 0)
        endVector = CIVector(x: 0, y: size.height)
    case .UpRight:
        startVector = CIVector(x: 0, y: 0)
        endVector = CIVector(x: size.width, y: size.height)
    }

    filter!.setValue(startVector, forKey: "inputPoint0")
    filter!.setValue(endVector, forKey: "inputPoint1")
    filter!.setValue(color1, forKey: "inputColor0")
    filter!.setValue(color2, forKey: "inputColor1")

    let image = UIImage(cgImage: context.createCGImage(filter!.outputImage!, from: CGRect(x: 0, y: 0, width: size.width, height: size.height))!)
    return image
}
CodyMace
  • 646
  • 1
  • 8
  • 19
3
yourLabel.textColor = UIColor(patternImage: UIImage(named: "ur gradient image name ")!)
YodagamaHeshan
  • 4,996
  • 2
  • 26
  • 36
2

Simplest Swift 3 Solution

Add an image to your project assets or create one programmatically then do the following:

let image = UIImage(named: "myGradient.png")!
label.textColor = UIColor.init(patternImage: image)
Ben Sullivan
  • 2,134
  • 1
  • 18
  • 32
2

SwiftUI

Although we use Text in SwiftUI instead of UILabel, If you consider how to apply a gradient on a Text, you should apply it as a mask. But since gradients are stretchable, you can make a simple extension like this:

extension View {
    func selfSizeMask<T: View>(_ mask: T) -> some View {
        ZStack {
            self.opacity(0)
            mask.mask(self)
        }.fixedSize()
    }
}

Demo

And then you can assign any gradient or other type of view as a self-size mask like:

Text("Gradient is on FIRE !!!")
    .selfSizeMask(
        LinearGradient(
            gradient: Gradient(colors: [.red, .yellow]),
            startPoint: .bottom,
            endPoint: .top)
    )

Demo

This method contains some bonus advantages that you can see here in this answer

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
-1

You could sub-class out UILable and do the draw method yourself. That would probably be the more difficult approach, there might be an easier way.

Ryan Detzel
  • 5,519
  • 9
  • 37
  • 49