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
.
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!
}
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)
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.