2

I read this and this but they don't seem to address the issue. And the answers to this seem to go off a tangent in NSCoding and friends, so ...

Consider:

- (NSAttributedString *) attributedStringForText: (NSString *) text {
    NSMutableAttributedString* attrStr = [[NSMutableAttributedString alloc] initWithString:text];
    UIFont * bigF = [UIFont fontWithName:@"OpenSans-Extrabold" size:20.0f] ;
    UIFont * smaF = [UIFont fontWithName:@"OpenSans-Extrabold" size:12.0f] ;

    [attrStr addAttribute:NSFontAttributeName value:bigF range:(NSRange){0, 3}] ;
    [attrStr addAttribute:NSFontAttributeName value:smaF range:(NSRange){3, 6}] ;
    [attrStr addAttribute:NSFontAttributeName value:bigF range:(NSRange){6, [text length]-6}] ;

    [attrStr addAttribute:NSBackgroundColorAttributeName value:[UIColor brownColor] range:(NSRange){3, 6}] ;
    return attrStr ;
}

- (CALayer *) stealLayerFromUILabelForText: (NSString *) text inRect: (CGRect) bounds {
    UILabel * label = [[UILabel alloc] initWithFrame:bounds] ;
    label.textColor = [UIColor whiteColor] ;
    label.font = [UIFont fontWithName:@"OpenSans-Extrabold" size:20.0f] ;
    label.attributedText = [self attributedStringForText:text] ;
    [label sizeToFit] ;
    [label.layer display] ; // Yup!
    return label.layer ;
}

and

- (void) setupLayer: (CALayer *) tileLayer
               text: (NSString *) text
              index: (NSUInteger) index {

    CGRect (^center_rect)(CGRect, CGRect) = ^CGRect (CGRect r, CGRect into) {
        CGRect centered = into ;
        centered.origin.x += (into.size.width - r.size.width) / 2.0f ;
        centered.origin.y += (into.size.height - r.size.height) / 2.0f ;
        centered.size = r.size ;
        return centered ;
    } ;

    CALayer * layer = [self stealLayerFromUILabelForText:text inRect:tileLayer.bounds] ;
    CGRect frame = center_rect(layer.bounds, tileLayer.bounds) ;

    [tileLayer addSublayer:layer] ;

    layer.frame = frame ;
}

This works:

enter image description here

But I am concerned that I am somehow abusing UILabel & CALayer. On one hand UIView, UILabel and all are just a thin touch aware veneer on top of the real CALayer hierarchy. OTOH I am somehow relying on probably many not even stated assumptions here.

Anyone sees why this would break further down the road, or, better, can prove that it won't?

Community
  • 1
  • 1
verec
  • 5,224
  • 5
  • 33
  • 40
  • 1
    Something to consider - the first line of documentation for [CALayer display] states "Do not call this method directly." This means that if you do and Apple changes something in the future it could break. Maybe not likely but definitely possible. – nsdebug Apr 19 '14 at 15:31
  • There's a precedent in 'iOS Core Animation: Advanced Techniques By Nick Lockwood': http://books.google.co.uk/books?id=QfhdAAAAQBAJ&pg=PT117&lpg=PT117&dq=we+should+really+derive+these+from+the+UILabel+settings+too+but+that%27s+complicated,+so+for+now+we%27ll+just+hard-code+them&source=bl&ots=O6wUJ7l349&sig=zw23tDc5QeUYABIBtAVMg2eEQCc&hl=en&sa=X&ei=55dSU_jNNMWjO-ntgNgL&ved=0CC8Q6AEwAA#v=onepage&q=we%20should%20really%20derive%20these%20from%20the%20UILabel%20settings%20too%20but%20that's%20complicated%2C%20so%20for%20now%20we'll%20just%20hard-code%20them&f=false ... – verec Apr 19 '14 at 15:38
  • 1
    you could use the snapshot API in iOS 7 – Felix Apr 19 '14 at 16:28
  • Could you elaborate? The snapshot API would give me a rasterised bitmap, and I would need an offscreen view to snapshot from. None of which seem very palatable to me. It sure is interesting to explore other ways to do the same thing, but I'm more after reasons that would invalidate (or validate if I'm really lucky :-)) the layer stealing approach. – verec Apr 19 '14 at 16:38

2 Answers2

1

I've used this technique since 2013 and it's still stable. So yes, I think this is safe for now. Previously I was doing this without calling -display on iOS 4 or 5, which no longer works when compiled against newer SDKs, so thanks for this tidbit.

There is more information here if you're looking for other options: Add text to CALayer

Community
  • 1
  • 1
Matt Foley
  • 921
  • 1
  • 8
  • 24
0

This technique still works in iOS 10. IMO a UIView(in this case UILabel) is simply a delegate of its backing CALayer, so it should be safe to manipulate the underlying CALayer directly. The only problem is if you don't add the label as a subview, the label would be released and the underlying layer.delegate would become nil and the text never gets a chance to be rendered.

So we need a way to force rendering before the label is released. However it is worth mentioning that [CALayer display] should NOT be called directly per Apple's documentation. So based on the same idea, consider a safe alternative [CALayer displayIfNeeded].

- (CALayer *) stealLayerFromUILabelForText: (NSString *) text inRect: (CGRect) bounds {
    UILabel * label = [[UILabel alloc] initWithFrame:bounds] ;
    label.textColor = [UIColor whiteColor] ;
    label.font = [UIFont fontWithName:@"OpenSans-Extrabold" size:20.0f] ;
    label.attributedText = [self attributedStringForText:text] ;
    [label sizeToFit] ;
    [label.layer displayIfNeeded] ;
    return label.layer ;
}