Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix the Gradient on text when using an image in swift, as the gradient restarts

I'm trying to create a gradient on text, I have used UIGraphics to use a gradient image to create this. The problem I'm having is that the gradient is restarting. Does anyone know how I can scale the gradient to stretch to the text?

The text is on a wireframe and will be altered a couple of times. Sometimes it will be perfect but other times it is not.

The gradient should go yellow to blue but it restarts see photo below:

image

import UIKit

func colourTextWithGrad(label: UILabel) {
    UIGraphicsBeginImageContext(label.frame.size)
    UIImage(named: "testt.png")?.drawInRect(label.bounds)
    let myGradient: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    label.textColor = UIColor(patternImage: myGradient)

}
like image 574
Oliver Cavanagh Avatar asked Apr 09 '16 11:04

Oliver Cavanagh


1 Answers

You'll have to redraw the image each time the label size changes

This is because a pattered UIColor is only ever tiled. From the documentation:

During drawing, the image in the pattern color is tiled as necessary to cover the given area.

Therefore, you'll need to change the image size yourself when the bounds of the label changes – as pattern images don't support stretching. To do this, you can subclass UILabel, and override the layoutSubviews method. Something like this should achieve the desired result:

class GradientLabel: UILabel {
    
    let gradientImage = UIImage(named:"gradient.png")
    
    override func layoutSubviews() {
        
        guard let grad = gradientImage else { // skip re-drawing gradient if it doesn't exist
            return
        }
        
        // redraw your gradient image
        UIGraphicsBeginImageContext(frame.size)
        grad.drawInRect(bounds)
        let myGradient = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        // update text color
        textColor = UIColor(patternImage: myGradient)
    }
}

Although it's worth noting that I'd always prefer to draw a gradient myself – as you can have much more flexibility (say you want to add another color later). Also the quality of your image might be degraded when you redraw it at different sizes (although due to the nature of gradients, this should be fairly minimal).

You can draw your own gradient fairly simply by overriding the drawRect of your UILabel subclass. For example:

override func drawRect(rect: CGRect) {
    
    // begin new image context to let the superclass draw the text in (so we can use it as a mask)
    UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
    do {
        // get your image context
        let ctx = UIGraphicsGetCurrentContext()
        
        // flip context
        CGContextScaleCTM(ctx, 1, -1)
        CGContextTranslateCTM(ctx, 0, -bounds.size.height)
        
        // get the superclass to draw text
        super.drawRect(rect)
    }
    
    // get image and end context
    let img = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    // get drawRect context
    let ctx = UIGraphicsGetCurrentContext()
    
    // clip context to image
    CGContextClipToMask(ctx, bounds, img.CGImage)
    
    // define your colors and locations
    let colors = [UIColor.orangeColor().CGColor, UIColor.redColor().CGColor, UIColor.purpleColor().CGColor, UIColor.blueColor().CGColor]
    let locs:[CGFloat] = [0.0, 0.3, 0.6, 1.0]
    
    // create your gradient
    let grad = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), colors, locs)
    
    // draw gradient
    CGContextDrawLinearGradient(ctx, grad, CGPoint(x: 0, y:bounds.size.height*0.5), CGPoint(x:bounds.size.width, y:bounds.size.height*0.5), CGGradientDrawingOptions(rawValue: 0))

}

Output:

output


Swift 4 & as subclass

class GradientLabel: UILabel {
    
    // MARK: - Colors to create gradient from
    @IBInspectable open var gradientFrom: UIColor?
    @IBInspectable open var gradientTo: UIColor?
    
    override func draw(_ rect: CGRect) {
        // begin new image context to let the superclass draw the text in (so we can use it as a mask)
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
        do {
            // get your image context
            guard let ctx = UIGraphicsGetCurrentContext() else { super.draw(rect); return }
            // flip context
            ctx.scaleBy(x: 1, y: -1)
            ctx.translateBy(x: 0, y: -bounds.size.height)
            // get the superclass to draw text
            super.draw(rect)
        }
        // get image and end context
        guard let img = UIGraphicsGetImageFromCurrentImageContext(), img.cgImage != nil else { return }
        UIGraphicsEndImageContext()
        // get drawRect context
        guard let ctx = UIGraphicsGetCurrentContext() else { return }
        // clip context to image
        ctx.clip(to: bounds, mask: img.cgImage!)
        // define your colors and locations
        let colors: [CGColor] = [UIColor.orange.cgColor, UIColor.red.cgColor, UIColor.purple.cgColor, UIColor.blue.cgColor]
        let locs: [CGFloat] = [0.0, 0.3, 0.6, 1.0]
        // create your gradient
        guard let grad = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locs) else { return }
        // draw gradient
        ctx.drawLinearGradient(grad, start: CGPoint(x: 0, y: bounds.size.height*0.5), end: CGPoint(x:bounds.size.width, y: bounds.size.height*0.5), options: CGGradientDrawingOptions(rawValue: 0))
    }
}
like image 124
Hamish Avatar answered Sep 22 '22 06:09

Hamish