我希望将UILabel(最好通过子类化)绘制为透明标签,但背景为实心。我画了一个简单的例子(对不起,这很难看,但它得到的结论是:)。)。
基本上我有一个UILabel,我希望背景是一个设定的颜色,文字应该是透明的。我不想用视图背景为文本着色,而是让它是100%透明的,因为我在背景中有一个纹理,我想确保在标签的内部和外部排列。
我一直在深夜浏览SO并在Google上搜索,但我找不到有用的消息来源。我对CG绘图没有太多经验,所以我很感激任何链接,帮助,教程或示例代码(也许Apple有一些我需要看一下?)。
非常感谢!
答案 0 :(得分:12)
我使用几乎所有代码将其重写为UILabel子类并将其发布在GitHub
它的要点是你覆盖drawRect但是调用[super drawRect:rect]
让UILabel正常渲染。使用白色标签颜色可以轻松地将标签本身用作蒙版。
- (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);
}
答案 1 :(得分:4)
使用CALayer面具解决。创建标准蒙版(例如墙纸文本)很简单。为了创建被淘汰的文本,我不得不反转我的蒙版的alpha通道,其中包括将标签渲染到CGImageRef然后进行像素推送。
示例应用程序可在此处获取:https://github.com/robinsenior/RSMaskedLabel
相关代码是为了避免将来链接腐烂:
#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
答案 2 :(得分:4)
这是一种类似于Matt Gallagher的技术,它将生成带图像的反转文本掩码。
分配(可变)数据缓冲区。使用8位Alpha通道创建位图上下文。配置文本绘图的设置。在复制模式下填充整个缓冲区(默认颜色假定为alpha值为1)。以明文模式写入文本(alpha值为0)。从位图上下文创建图像。使用位图作为蒙版从源图像创建新图像。创建一个新的UIImage并清理。
每次textString或sourceImage或size值更改时,都会重新生成最终图像。
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);
另一种方法是将textImage放入新图层并在视图图层上设置该图层。 (删除创建“newImage”和“finalImage”的行。)假设这发生在视图代码中的某处:
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;
还有更多选择,有些可能更好(子类UIImage并在超类完成绘图后直接以清除模式绘制文本?)。
答案 3 :(得分:0)
Swift 5 解决方案(Xcode:12.5):
class MaskedLabel: UILabel {
var maskColor : UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
customInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
customInit()
}
func customInit() {
maskColor = self.backgroundColor
self.textColor = UIColor.white
backgroundColor = UIColor.clear
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
super.draw(rect)
context.concatenate(__CGAffineTransformMake(1, 0, 0, -1, 0, rect.height))
let image: CGImage = context.makeImage()!
let mask: CGImage = CGImage(maskWidth: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, provider: image.dataProvider!, decode: image.decode, shouldInterpolate: image.shouldInterpolate)!
context.clear(rect)
context.saveGState()
context.clip(to: rect, mask: mask)
if (self.layer.cornerRadius != 0.0) {
context.addPath(CGPath(roundedRect: rect, cornerWidth: self.layer.cornerRadius, cornerHeight: self.layer.cornerRadius, transform: nil))
context.clip()
}
drawBackgroundInRect(rect: rect)
context.restoreGState()
}
func drawBackgroundInRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
if let _ = maskColor {
maskColor!.set()
}
context!.fill(rect)
}
}