34

Is it possible to add a UILabel to a CALayer without subclassing and drawing it in drawInContext:?

Thanks!

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
christo16
  • 4,843
  • 5
  • 42
  • 53

8 Answers8

154
CATextLayer *label = [[CATextLayer alloc] init];
[label setFont:@"Helvetica-Bold"];
[label setFontSize:20];  
[label setFrame:validFrame];
[label setString:@"Hello"];
[label setAlignmentMode:kCAAlignmentCenter];
[label setForegroundColor:[[UIColor whiteColor] CGColor]];
[layer addSublayer:label];

[label release];
Sangram Shivankar
  • 3,535
  • 3
  • 26
  • 38
Mshah2
  • 2,169
  • 2
  • 14
  • 4
  • 4
    IMHO, this is a simpler solution than a custom CALayer or layer delegate as long as you don't need more than basic text drawing. CATextLayer already knows how to draw text, so there's no need to reinvent the wheel. – Dalbergia Dec 21 '11 at 20:43
  • @Mshah2 why the text is comming with non-antialiasing effect? the text is looking like its been magnified – sreekanthk Dec 19 '14 at 07:40
  • 20
    You need to adjust the content scale of the CALayer otherwise you'll get a blurry text on retina (@2x) or @3x devices. Use: label.contentsScale = [UIScreen mainScreen].scale; – yonel Jan 28 '15 at 14:30
  • As usual the most useful answer at the bottom. Thanks StackOverflow! – bicycle Feb 16 '16 at 22:20
  • 4
    @bicycle, come on man, if it wasn't for Stack Overflow we'd all be without a job – Adam Waite Apr 14 '16 at 13:25
39

I don't think you can add a UIView subclass to a CALayer object. However if you want to draw text on a CALayer object, it can be done using the drawing functions provided in NSString UIKit additions as shown below. While my code is done in the delegate's drawLayer:inContext method, the same can be used in subclass' drawInContext: method. Is there any specific UILabel functionality that you want to leverage?

- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
  CGContextSetFillColorWithColor(ctx, [[UIColor darkTextColor] CGColor]);

  UIGraphicsPushContext(ctx);
  /*[word drawInRect:layer.bounds 
          withFont:[UIFont systemFontOfSize:32] 
     lineBreakMode:UILineBreakModeWordWrap 
         alignment:UITextAlignmentCenter];*/

  [word drawAtPoint:CGPointMake(30.0f, 30.0f) 
           forWidth:200.0f 
           withFont:[UIFont boldSystemFontOfSize:32] 
      lineBreakMode:UILineBreakModeClip];

  UIGraphicsPopContext();
}
Deepak Danduprolu
  • 44,595
  • 12
  • 101
  • 105
12

Just to document my approach, I did it like this in Swift 4+ :

let textlayer = CATextLayer()

textlayer.frame = CGRect(x: 20, y: 20, width: 200, height: 18)
textlayer.fontSize = 12
textlayer.alignmentMode = .center
textlayer.string = stringValue
textlayer.isWrapped = true
textlayer.truncationMode = .end
textlayer.backgroundColor = UIColor.white.cgColor
textlayer.foregroundColor = UIColor.black.cgColor

caLayer.addSublayer(textlayer) // caLayer is and instance of parent CALayer 
Faisal Rahman Avash
  • 1,268
  • 9
  • 13
5

Your UILabel already has a CALayer behind it. If you are putting together several CALayers, you can just add the UILabel's layer as a sublayer of one of those (by using its layer property).

If it's direct text drawing in a layer that you want, the UIKit NSString additions that Deepak points to are the way to go. For an example of this in action, the Core Plot framework has a Mac / iPhone platform-independent CALayer subclass which does text rendering, CPTextLayer.

Otávio
  • 735
  • 11
  • 23
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • Hey Brad, I actually used to use this technique, (adding a UILabel's layer to a CALayer) and it seems that maybe it's stopped working in the iOS 8 base SDK? I've started rebuilding an old app I created that exports videos, and the sublayers of the parent CALayer seem to stay nil with this old code. Could be something else wrong, going to keep looking. – Matt Foley May 17 '15 at 21:44
  • Fixed it. For anyone else who comes along looking for how to make this work compiled against iOS 8 Base SDK, call [label.layer display] before adding the label's layer to another CALayer. Credit: http://stackoverflow.com/questions/23171790/is-it-safe-to-add-a-stolen-uilabels-layer-to-some-other-calayers-sublayer – Matt Foley May 17 '15 at 22:17
3

Add a CATextLayer as a sublayer and set the string property. That would be easiest and you can easily use a layout manager to make it very generic.

Alec Sloman
  • 699
  • 4
  • 18
  • 2
    ... _can easily use a layout manager_... Well, in MacOS, yes, but not in iOS. CALayers in iOS don't support layout managers: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/Layout.html – Dalbergia Dec 21 '11 at 20:43
1

The answers below are fine, just make sure you add otherwise you text will be blurry:

textLayer.contentsScale = UIScreen.main.scale

Final code for Swift:

let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 0, y: 0, width: 60, height: 15)
textLayer.fontSize = 12
textLayer.string = "my text"
textLayer.foregroundColor = UIColor.red.cgColor
textLayer.contentsScale = UIScreen.main.scale
pierre23
  • 3,846
  • 1
  • 28
  • 28
-1
class MyCALayer: CALayer {

    ......

    override func draw(in ctx: CGContext) {
        UIGraphicsPushContext(ctx)
        let text = "這是一段普通的文字"
        let textAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.blue]
        var drawPoint = CGPoint(x: 0, y: 0)
        for char in text {
            let word = NSAttributedString(string: String(char),
                                          attributes: textAttrs)
            let wordBounds = word.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
            word.draw(at: drawPoint)
            drawPoint = CGPoint(x: drawPoint.x + wordBounds.width, y: drawPoint.y)
        
            let whitespace = NSAttributedString(string: " ", attributes: textAttrs)
            let whitespaceBounds = whitespace.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
            whitespace.draw(at: drawPoint)
            drawPoint = CGPoint(x: drawPoint.x + whitespaceBounds.width, y: drawPoint.y)
        }
    
        UIGraphicsPopContext()
    }

    ......
}

The result

enter image description here

Cable W
  • 633
  • 1
  • 8
  • 17
-2

Always remember to remove previous sublayers, if you gonna add another one, to prevent duplicating views:

if let sublayers = layer.sublayers {
    for sublayer in sublayers {
        sublayer.removeFromSuperlayer()
    }
}
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358