2012-01-04 5 views
11

Je cherche à dessiner un UILabel (préférable à travers le sous-classement) comme une étiquette transparente, mais avec un fond solide. Je dresse un rapide exemple (désolé, c'est moche, mais ça fait passer les points :)).drawRect dessin texte 'transparent'?

Fondamentalement, j'ai un UILabel et je voudrais que l'arrière-plan soit une couleur fixe, et le texte devrait être transparent. Je ne veux pas colorier le texte avec l'arrière-plan des vues, mais plutôt le rendre transparent à 100%, car j'ai une texture en arrière-plan que je veux m'assurer que les lignes soient à l'intérieur et à l'extérieur de l'étiquette.

J'ai passé la nuit à naviguer sur Internet et à faire des recherches sur Google, mais je n'ai trouvé aucune source utile. Je n'ai pas beaucoup d'expérience avec le dessin de CG, donc j'apprécierais n'importe quels liens, aide, tutoriel ou exemple de code (peut-être qu'Apple en a besoin?).

Merci beaucoup!

enter image description here

+0

Jusqu'à présent, de la plongée plus loin dans les docs passionnants de quartz, je crois que je dois utiliser '' CGContextSetTextDrawingMode' et kCGTextClip' – runmad

+0

Voir ma réponse à http://stackoverflow.com/questions/19787238/transparent- uilabel-textcolor-on-superview-superview-sort-of pour une approche basée sur le chemin de ce problème. –

Répondre

12

Je l'ai réécrite comme une sous-classe UILabel utilisant à peine un code et affiché sur GitHub

L'essentiel de c'est vous substituez drawRect mais appelez [super drawRect:rect] pour laisser le UILabel rendre normalement. L'utilisation d'une couleur d'étiquette blanche vous permet d'utiliser facilement l'étiquette elle-même comme masque.

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    // let the superclass draw the label normally 
    [super drawRect:rect]; 

    CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect))); 

    // create a mask from the normally rendered text 
    CGImageRef image = CGBitmapContextCreateImage(context); 
    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image)); 

    CFRelease(image); image = NULL; 

    // wipe the slate clean 
    CGContextClearRect(context, rect); 

    CGContextSaveGState(context); 
    CGContextClipToMask(context, rect, mask); 

    CFRelease(mask); mask = NULL; 

    [self RS_drawBackgroundInRect:rect]; 

    CGContextRestoreGState(context); 

} 
4

masques résolus à l'aide CALayer. Créer un masque standard (texte tapissé, par exemple) est simple. Pour créer le texte assommé, j'ai dû inverser le canal alpha de mon masque, ce qui impliquait de rendre un label à un CGImageRef et ensuite de faire du pixel-pushing.

sample mask

Exemple d'application est disponible ici: https://github.com/robinsenior/RSMaskedLabel

code approprié est ici pour éviter de futurs lien pourriture:

#import "RSMaskedLabel.h" 
#import <QuartzCore/QuartzCore.h> 

@interface UIImage (RSAdditions) 
+ (UIImage *) imageWithView:(UIView *)view; 
- (UIImage *) invertAlpha; 
@end 

@interface RSMaskedLabel() 
{ 
    CGImageRef invertedAlphaImage; 
} 
@property (nonatomic, retain) UILabel *knockoutLabel; 
@property (nonatomic, retain) CALayer *textLayer; 
- (void) RS_commonInit; 
@end 

@implementation RSMaskedLabel 
@synthesize knockoutLabel, textLayer; 

- (id)initWithFrame:(CGRect)frame 
{ 
    self = [super initWithFrame:frame]; 
    if (self) 
    { 
     [self RS_commonInit]; 
    } 
    return self; 
} 

- (id)initWithCoder:(NSCoder *)aDecoder 
{ 
    self = [super initWithCoder:aDecoder]; 
    if (self) 
    { 
     [self RS_commonInit]; 
    } 
    return self; 
} 

+ (Class)layerClass 
{ 
    return [CAGradientLayer class]; 
} 

- (void) RS_commonInit 
{ 
    [self setBackgroundColor:[UIColor clearColor]]; 

    // create the UILabel for the text 
    knockoutLabel = [[UILabel alloc] initWithFrame:[self frame]]; 
    [knockoutLabel setText:@"booyah"]; 
    [knockoutLabel setTextAlignment:UITextAlignmentCenter]; 
    [knockoutLabel setFont:[UIFont boldSystemFontOfSize:72.0]]; 
    [knockoutLabel setNumberOfLines:1]; 
    [knockoutLabel setBackgroundColor:[UIColor clearColor]]; 
    [knockoutLabel setTextColor:[UIColor whiteColor]]; 

    // create our filled area (in this case a gradient) 
    NSArray *colors = [[NSArray arrayWithObjects: 
         (id)[[UIColor colorWithRed:0.349 green:0.365 blue:0.376 alpha:1.000] CGColor], 
         (id)[[UIColor colorWithRed:0.455 green:0.490 blue:0.518 alpha:1.000] CGColor], 
         (id)[[UIColor colorWithRed:0.412 green:0.427 blue:0.439 alpha:1.000] CGColor], 
         (id)[[UIColor colorWithRed:0.208 green:0.224 blue:0.235 alpha:1.000] CGColor], 
         nil] retain]; 

    NSArray *gradientLocations = [NSArray arrayWithObjects: 
            [NSNumber numberWithFloat:0.0], 
            [NSNumber numberWithFloat:0.54], 
            [NSNumber numberWithFloat:0.55], 
            [NSNumber numberWithFloat:1], nil]; 

    // render our label to a UIImage 
    // if you remove the call to invertAlpha it will mask the text 
    invertedAlphaImage = [[[UIImage imageWithView:knockoutLabel] invertAlpha] CGImage]; 

    // create a new CALayer to use as the mask 
    textLayer = [CALayer layer]; 
    // stick the image in the layer 
    [textLayer setContents:(id)invertedAlphaImage]; 

    // create a nice gradient layer to use as our fill 
    CAGradientLayer *gradientLayer = (CAGradientLayer *)[self layer]; 

    [gradientLayer setBackgroundColor:[[UIColor clearColor] CGColor]]; 
    [gradientLayer setColors: colors]; 
    [gradientLayer setLocations:gradientLocations]; 
    [gradientLayer setStartPoint:CGPointMake(0.0, 0.0)]; 
    [gradientLayer setEndPoint:CGPointMake(0.0, 1.0)]; 
    [gradientLayer setCornerRadius:10]; 

    // mask the text layer onto our gradient 
    [gradientLayer setMask:textLayer]; 
} 

- (void)layoutSubviews 
{ 
    // resize the text layer 
    [textLayer setFrame:[self bounds]]; 
} 

- (void)dealloc 
{ 
    CGImageRelease(invertedAlphaImage); 
    [knockoutLabel release]; 
    [textLayer  release]; 
    [super   dealloc]; 
} 

@end 

@implementation UIImage (RSAdditions) 

/* 
create a UIImage from a UIView 
*/ 
+ (UIImage *) imageWithView:(UIView *)view 
{ 
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0); 
    [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext(); 

    UIGraphicsEndImageContext(); 

    return img; 
} 

/* 
get the image to invert its alpha channel 
*/ 
- (UIImage *)invertAlpha 
{ 
    // scale is needed for retina devices 
    CGFloat scale = [self scale]; 
    CGSize size = self.size; 
    int width = size.width * scale; 
    int height = size.height * scale; 

    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); 

    unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1); 

    CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast); 

    CGColorSpaceRelease(colourSpace); 

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]); 

    for(int y = 0; y < height; y++) 
    { 
     unsigned char *linePointer = &memoryPool[y * width * 4]; 

     for(int x = 0; x < width; x++) 
     { 
      linePointer[3] = 255-linePointer[3]; 
      linePointer += 4; 
     } 
    } 

    // get a CG image from the context, wrap that into a 
    CGImageRef cgImage = CGBitmapContextCreateImage(context); 
    UIImage *returnImage = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp]; 

    // clean up 
    CGImageRelease(cgImage); 
    CGContextRelease(context); 
    free(memoryPool); 

    // and return 
    return returnImage; 
} 
@end 
4

est ici une technique qui est similaire à Matt Gallagher, qui va générer un masque de texte inversé avec une image.

Allouer un tampon de données (mutable). Créez un contexte bitmap avec un canal alpha de 8 bits. Configurez les paramètres pour le dessin de texte. Remplissez le tampon entier en mode copie (couleur par défaut supposée avoir une valeur alpha de 1). Ecrire le texte en mode clair (valeur alpha de 0). Créer une image à partir du contexte bitmap. Utilisez le bitmap comme masque pour créer une nouvelle image à partir de l'image source. Créez un nouveau UIImage et nettoyez-le.

Chaque fois que les valeurs textString ou sourceImage ou size changent, recréez l'image finale.

CGSize size = /* assume this exists */; 
UIImage *sourceImage = /* assume this exists */; 
NSString *textString = /* assume this exists */; 
char *text = [textString cStringUsingEncoding:NSMacOSRomanStringEncoding]; 
NSUInteger len = [textString lengthOfBytesUsingEncoding:cStringUsingEncoding:NSMacOSRomanStringEncoding]; 

NSMutableData *data = [NSMutableData dataWithLength:size.width*size.height*1]; 
CGContextRef context = CGBitmapContextCreate([data mutableBytes], size.width, size.height, 8, size.width, NULL, kCGImageAlphaOnly); 

CGContextSelectFont(context, "Gill Sans Bold", 64.0f, kCGEncodingMacRoman); 
CGContextSetTextDrawingMode(context, kCGTextFill); 

CGContextSetBlendMode(context, kCGBlendModeCopy); 
CGContextFillRect(context, overlay.bounds); 
CGContextSetBlendMode(context, kCGBlendModeClear); 
CGContextShowTextAtPoint(context, 16.0f, 16.0f, text, len); 

CGImageRef textImage = CGBitmapContextCreateImage(context); 
CGImageRef newImage = CGImageCreateWithMask(sourceImage.CGImage, textImage); 

UIImage *finalImage = [UIImage imageWithCGImage:newImage]; 

CGContextRelease(context); 
CFRelease(newImage); 
CFRelease(textImage); 

Une autre façon de faire consiste à mettre l'textImage dans une nouvelle couche et la mise en ce calque sur la couche de votre point de vue. (Supprimez les lignes qui créent « newImage » et « finalImage ».) En supposant que cela se produit quelque part dans le code de votre vue:

CALayer *maskLayer = [[CALayer alloc] init]; 
CGPoint position = CGPointZero; 

// layout the new layer 
position = overlay.layer.position; 
position.y *= 0.5f; 
maskLayer.bounds = overlay.layer.bounds; 
maskLayer.position = position; 
maskLayer.contents = (__bridge id)textImage; 

self.layer.mask = maskLayer; 

Il y a plus d'alternatives, certains pourraient être mieux (sous-classe UIImage et dessiner le texte directement en clair mode après que la superclasse a fait son dessin?).

+0

La nouvelle réponse de Robin Senior est la meilleure réponse, maintenant! –