2012-01-04 20 views
11

Sto cercando di disegnare un UILabel (preferibile tramite sottoclasse) come etichetta trasparente, ma con uno sfondo uniforme. Traccio un rapido esempio (scusate, è brutto, ma ottiene i punti attraverso :)).drawRect disegna il testo 'trasparente'?

Fondamentalmente ho un UILabel e vorrei che lo sfondo fosse un colore impostato, e il testo dovrebbe essere trasparente. Non voglio colorare il testo con lo sfondo delle viste, ma preferisco che sia trasparente al 100%, dal momento che ho una texture in background che voglio assicurarmi che le righe siano all'interno e all'esterno dell'etichetta.

Ho passato la notte a navigare SO e a cercare su Google, ma non ho trovato alcuna fonte utile. Non ho molta esperienza con il disegno in CG, quindi apprezzerei qualsiasi link, guida, tutorial o codice di esempio (forse Apple ne ha alcuni di cui ho bisogno di dare un'occhiata?).

Grazie mille!

enter image description here

+0

Finora, dalle immersioni ulteriormente nella documentazione in quarzo eccitanti, credo che ho bisogno di usare '' CGContextSetTextDrawingMode' e kCGTextClip' – runmad

+0

Vedere la mia risposta a http://stackoverflow.com/questions/19787238/transparent- uilabel-textcolor-on-superview-superview-sort-of per un approccio basato su path a questo problema. –

risposta

12

Ho riscritto come una sottoclasse UILabel utilizzando malapena qualsiasi codice e postato su GitHub

L'essenza di esso è di ignorare drawRect ma chiama [super drawRect:rect] di lasciare che l'UILabel rendere come normale. L'utilizzo di un colore dell'etichetta bianco consente di utilizzare facilmente l'etichetta stessa come maschera.

- (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

risolto utilizzando maschere CALayer. La creazione di una maschera standard (ad esempio, il testo su sfondo) è semplice. Per creare il testo eliminato, ho dovuto invertire il canale alfa della mia maschera, che implicava il rendering di un'etichetta su un CGImageRef e quindi l'esecuzione di alcuni pixel-push.

sample mask

Esempio di applicazione è disponibile qui: https://github.com/robinsenior/RSMaskedLabel

codice relativo è qui per evitare futuro collegamento-rot:

#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

Ecco una tecnica che è simile a Matt Gallagher, che genererà una maschera di testo invertita con un'immagine.

Assegnare un buffer di dati (modificabile). Creare un contesto bitmap con un canale alfa a 8 bit. Configura le impostazioni per il disegno del testo. Riempi l'intero buffer in modalità copia (il colore predefinito assume il valore alfa 1). Scrivi il testo in modalità chiara (valore alfa di 0). Crea un'immagine dal contesto bitmap. Usa la bitmap come maschera per creare una nuova immagine dall'immagine sorgente. Crea un nuovo UIImage e ripulisci.

Ogni volta che i valori textString o sourceImage o size cambiano, ricreare l'immagine 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); 

Un altro modo per farlo consiste nel mettere il textImage in un nuovo livello e l'impostazione che strato su strato del vostro punto di vista. (Rimuovere le linee che creano "newImage" e "finalImage".) Supponendo che questo accade all'interno del codice del vostro vista da qualche parte:

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; 

Non ci sono più alternative, alcuni potrebbe essere migliore (sottoclasse UIImage e disegnare il testo direttamente in chiaro modalità dopo che la superclasse ha fatto il suo disegno?).

+0

La nuova risposta di Robin Senior è la migliore risposta, ora! –

Problemi correlati