Is it safe to add a “stolen” UILabel's layer t

2019-05-19 09:51发布

问题:

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:

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?

回答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



回答2:

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 ;
}