Search code examples
iosswiftcore-graphicscgcontextblending

How to draw a UILabel with a different blend mode in draw(_ rect: CGRect) in Swift


So I have a UILabel that is being drawn on top of a gradient image (that is a UIImageView). It looks sorta like this:

enter image description here

I'm trying to change the blendMode of the graphics context in the UILabel's draw(_ rect: CGRect) function so that it draws the label but blended with the background with a softLight blending mode.

Here is what I want it to look like:

enter image description here

Here is the code I have in the draw(_ rect: CGRect) function:

override func draw(_ rect: CGRect) {
    // Drawing code
     if let context = UIGraphicsGetCurrentContext() {
        context.setBlendMode(.softLight)
        self.textColor.set()
        self.drawText(in: rect)
   }
 }

This code just renders the top picture and not the blended version. This is not all that I've tried, I've tried so much other things but I just don't understand CGContext's and the draw function in particular.

Most answers on SO are either in old objective-c and are deprecated/broken or swift and too complicated (use Metal for example). I just want to draw the text in the context I have and set the context's blending mode to soft light. There has to be an easy way!

Any help on how to fix this would be appreciated. Thanks!


Solution

  • You can't blend views this way; they have separate layer hierarchies that are resolved independently. Move the gradient and text on CALayer objects and blend those (or draw them by hand inside the same view). For example:

    class MyView: UIView {
        let gradientLayer: CAGradientLayer = {
            let gradientLayer = CAGradientLayer()
            gradientLayer.colors = [UIColor.red.cgColor,
                                    UIColor.blue.cgColor]
            gradientLayer.startPoint = CGPoint(x: 0, y: 0)
            gradientLayer.endPoint = CGPoint(x: 1, y: 1)
            return gradientLayer
        }()
    
        let textLayer: CATextLayer = {
            let textLayer = CATextLayer()
            let astring = NSAttributedString(string: "Text Example",
                                             attributes: [.font: UIFont(name: "MarkerFelt-Wide", size: 60)!])
            textLayer.string = astring
            textLayer.alignmentMode = kCAAlignmentCenter
            textLayer.bounds = astring.boundingRect(with: CGSize(width: .max, height: .max),
                                                    options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
            return textLayer
        }()
    
        override func draw(_ rect: CGRect) {
            if let context = UIGraphicsGetCurrentContext() {
                gradientLayer.bounds = bounds
                gradientLayer.render(in: context)
    
                context.saveGState()
                context.setBlendMode(.softLight)
    
                context.translateBy(x: center.x - textLayer.bounds.width / 2,
                                    y: center.y - textLayer.bounds.height / 2)
    
                textLayer.position = center
                textLayer.render(in: context)
                context.restoreGState()
            }
        }
    }
    

    enter image description here