UILabel with color gradient to certain height of t

2019-06-04 23:56发布

问题:

I want to achieve the effect from the picture bellow - a UILabel that shows progress X as a gradient from bottom to X% of height of the label. The remaining (100 - X)% of the label will have a different color.

The only thing that comes into my mind right now is to create two UIViews one with gray background and one with the color of the gradient. Put the gradient view above the gray view and set it’s height to match the current progress. Then simply use the label as a mask for those two views. For better illustration I attached a picture describing my suggested solution. Though I’m not happy with it because it is not very elegant.



Is it possible to achieve this in a different and more elegant way? Ideally just by subclassing UILabel.

回答1:

You could do this with layers and masks, but it will actually be easier to just set the text color with a UIColor from pattern image. This code works, though it might be better to subclass UILabel and give the class a method to apply and or update the image. I say this is easier, because I find dealing with text layers a bit of a pain to get the sizing perfect, where as labels can alter their font size with adjustsFontSizeToFitWidth.

 override func viewDidLayoutSubviews() {
    label.textColor = UIColor(patternImage: partialGradient(forViewSize: label.frame.size, proportion: 0.65))
}

func partialGradient(forViewSize size: CGSize, proportion p: CGFloat) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)

    let context = UIGraphicsGetCurrentContext()


    context?.setFillColor(UIColor.darkGray.cgColor)
    context?.fill(CGRect(origin: .zero, size: size))

    let c1 = UIColor.orange.cgColor
    let c2 = UIColor.red.cgColor

    let top = CGPoint(x: 0, y: size.height * (1.0 - p))
    let bottom = CGPoint(x: 0, y: size.height)

    let colorspace = CGColorSpaceCreateDeviceRGB()

    if let gradient = CGGradient(colorsSpace: colorspace, colors: [c1, c2] as CFArray, locations: [0.0, 1.0]){
        // change 0.0 above to 1-p if you want the top of the gradient orange
        context?.drawLinearGradient(gradient, start: top, end: bottom, options: CGGradientDrawingOptions.drawsAfterEndLocation)
    }


    let img = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return img!
}



回答2:

You can use Core Animation with CATextLayer and CAGradientLayer.

import PlaygroundSupport

let bgView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
bgView.backgroundColor = UIColor.black
PlaygroundPage.current.liveView = bgView

let textLayer = CATextLayer()
textLayer.frame = bgView.frame
textLayer.string = "70"
textLayer.fontSize = 60

let gradientLayer = CAGradientLayer()
gradientLayer.frame = bgView.frame
gradientLayer.colors = [
    UIColor.gray.cgColor,
    UIColor(red: 1, green: 122.0/255.0, blue: 0, alpha: 1).cgColor,
    UIColor(red: 249.0/255.0, green: 1, blue: 0, alpha: 1).cgColor
]

//Here you can adjust the filling
gradientLayer.locations = [0.5, 0.51, 1]

gradientLayer.mask = textLayer
bgView.layer.addSublayer(gradientLayer)



回答3:

You can subclass UILabel and draw what you want in draw(_ rect: CGRect) function. Or if you want to do it fast you can subclass too and add gradient subview. Don't forget to resize it in layoutSubviews() function.