I have a modified UIView that I am using to display rich text (NSAttributedString). I am adding the UIView to my UITableViewCell and drawRect is being executed. I overrided that method in order to show text.
The problem is that in the row #3 it writes the text that I want but below it the old text is still there.
The same happens with all the other cells.
How can I clear my UIView for each cell?
This is my drawrect
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, self.bounds );
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.attString);
CTFrameRef frame =
CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, [self.attString length]), path, NULL);
CTFrameDraw(frame, context); //4
CFRelease(frame); //5
CFRelease(path);
CFRelease(framesetter);
}
And here is how I am adding it in cellforrowatindexpath:
NSAttributedString* attrString = [p attrStringFromMarkup:[NSString stringWithFormat:#"%#%# %#%# %#%#", #"<font color=\"black\">", userName, #"<font color=\"gray\">",actionType, #"<font color=\"black\">", object]];
CGRect rect = CGRectMake(0, 0, 249, 50);
CTView *aView = [[CTView alloc]initWithFrame:rect];
[aView setBackgroundColor:[UIColor clearColor]];
[(CTView*)aView setAttString: attrString];
[cell.feedView addSubview:aView];
You should create the CTView only once after creating the UITableViewCell and then reuse it with the cell. Otherwise you would add the CTView multiple times to the same cell.
The code should look like that:
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
CGRect rect = CGRectMake(0, 0, 249, 50);
CTView *aView = [[CTView alloc]initWithFrame:rect];
[aView setBackgroundColor:[UIColor clearColor]];
[aView setTag:kCTViewTag];
[cell.feedView addSubview:aView];
}
// Configure the cell...
NSAttributedString* attrString = [p attrStringFromMarkup:[NSString stringWithFormat:#"%#%# %#%# %#%#", #"<font color=\"black\">", userName, #"<font color=\"gray\">",actionType, #"<font color=\"black\">", object]];
CTView* view = (CTView*)[cell viewWithTag:kCTViewTag];
[view setAttString:attrString];
The setAttString method should invoke [self setNeedsDisplay].
Related
I converted in PDF an NSView (PDFContentView:NSView) with the method NSPrintOperation. This NSView contains 2 subviews (Drawing:NSView). This subviews are drawn using the drawRect method.
The expected result is correct when I display my view in a NSWindow :
But in the PDF file, the second subview seem to be included in the first :
It seems to be a problem of layers, but when I change setWantsLayer: to NO, it's the same result.
Nota : I found answers that recommend to convert the subviews, which are drawn by drawRect method, into NSImage and then add it to the View. But This solution don't fit my problem, because I need to keep the possibility of zooming in the pdf without pixelising the subviews.
Thank you for your help.
Here is my code :
The method to convert my pdfContentView in PDF :
- (void)createPDFWithPages
{
NSPrintInfo *printInfo;
NSPrintInfo *sharedInfo;
NSPrintOperation *printOp;
NSMutableDictionary *printInfoDict;
NSMutableDictionary *sharedDict;
sharedInfo = [NSPrintInfo sharedPrintInfo];
sharedDict = [sharedInfo dictionary];
printInfoDict = [NSMutableDictionary dictionaryWithDictionary:sharedDict];
[printInfoDict setObject:NSPrintSaveJob forKey:NSPrintJobDisposition];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *desktopURLArray = [fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask];
NSURL *desktopURL = [desktopURLArray objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"/%#", #"myPDF.pdf"];
NSURL *fileURL = [NSURL fileURLWithPath:[[desktopURL path] stringByAppendingString:fileName]];
[printInfoDict setObject:[fileURL path] forKey:NSPrintSavePath];
printInfo = [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
[printInfo setTopMargin:30.0];
[printInfo setBottomMargin:30.0];
[printInfo setLeftMargin:30.0];
[printInfo setRightMargin:30.0];
[printInfo setHorizontalPagination: NSAutoPagination];
[printInfo setVerticalPagination: NSAutoPagination];
[printInfo setVerticallyCentered:NO];
printOp = [NSPrintOperation printOperationWithView:pdfContentView printInfo:printInfo];
[printOp setShowsPrintPanel:NO];
[printOp runOperation];
}
PDFContentView.m :
#import "PDFContentView.h"
#import "Drawing.h"
#interface PDFContentView ()
{
Drawing *drawing1;
Drawing *drawing2;
}
#end
#implementation PDFContentView
- (id)init
{
if(self = [super init])
{
self.frame = NSMakeRect(10, 10, 520, 780); //page size = (520, 780)
[self setWantsLayer:YES];
self.layer.backgroundColor = [NSColor whiteColor].CGColor;
drawing1 = [[Drawing alloc] initWithFrame:NSMakeRect(0, 20, 50, 50)];
[drawing1 setBackgroundColor:[NSColor greenColor]];
[self addSubview:drawing1];
drawing2 = [[Drawing alloc] initWithFrame:NSMakeRect(30, 0, 50, 50)];
[drawing2 setBackgroundColor:[NSColor yellowColor]];
[self addSubview:drawing2];
}
return self;
}
- (BOOL)isFlipped
{
return YES;
}
#end
Drawing.m :
#import "Drawing.h"
#interface Drawing ()
{
NSColor *backgrounColor;
}
#end
#implementation Drawing
- (id)initWithFrame:(NSRect)contentRect
{
if(self = [super initWithFrame:(NSRect)contentRect])
{
self.frame = contentRect;
[self setWantsLayer:YES];
backgrounColor = [[NSColor alloc] init];
backgrounColor = [NSColor colorWithCalibratedRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:255.0/255.0];
}
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)setBackgroundColor:(NSColor*)color;
{
backgrounColor = color;
[self setNeedsDisplay:YES];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGMutablePathRef path00 = CGPathCreateMutable();
CGPathMoveToPoint(path00, NULL, 0, 0);
CGPathAddLineToPoint(path00, NULL, self.frame.size.width, 0);
CGPathAddLineToPoint(path00, NULL, self.frame.size.width, self.frame.size.height);
CGPathAddLineToPoint(path00, NULL, 0, self.frame.size.height);
CGPathCloseSubpath(path00);
CGContextSaveGState(context);
CGContextAddPath(context, path00);
CGContextSetFillColorWithColor(context, backgrounColor.CGColor);
CGContextFillPath(context);
CGContextRestoreGState(context);
CGRect borderRect = NSMakeRect(0.5, 0.5, 24, 24);
CGMutablePathRef borderPath = CGPathCreateMutable();
CGPathAddRect(borderPath, NULL, borderRect);
CGPathCloseSubpath(borderPath);
CGContextSaveGState(context);
CGContextAddPath(context, borderPath);
CGContextSetStrokeColorWithColor(context, [NSColor redColor].CGColor);
CGContextSetLineWidth(context, 1);
CGContextStrokePath(context);
CGContextRestoreGState(context); // => Missing funtion
CGMutablePathRef path2 = CGPathCreateMutable();
CGRect aRect = NSMakeRect(10, 11, 4, 4);
CGPathAddRect(path2, NULL, aRect);
CGPathCloseSubpath(path2);
CGContextSaveGState(context);
CGContextAddPath(context, path2);
CGContextSetShouldAntialias(context, NO);
CGContextSetStrokeColorWithColor(context, [NSColor blueColor].CGColor);
CGContextSetLineWidth(context, 1);
CGContextStrokePath(context);
CGContextRestoreGState(context); // => Missing funtion
}
#end
I'm really stuck on tinting an UIImage with a certain tint. The UIImage is in a custom TableView cell (if that makes any difference).
Looking at the picture, the UIImage has been replaced by 0.5 opacity yellow and the original image has been discarded. I want a tint on an existing image, not overwrite the image entirely. (p.s: the numbers in the picture are labels that are on top of the image, they are unrelated).
I realize there are similiar threads on stackoverflow and I must have tried out all the code that there is :-) Nothing is working. I don't expect anyone to post any code, just a push in the right direction!
Explanation of the code below:
myImage is the image
color is the color, probably yellow, with an opacity of 0.5
I use [util tintedImageWithColor] with both myImage and color.
It comes out as the picture above.
Does anyone have an idea, a push in the right direction?
Updated
MasterViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ArticleCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
Article *article = self.articles[indexPath.row];
Utils *util = [[Utils alloc] init];
// Populate cell components
UIImage *myImage = [UIImage imageWithData:article.post_image_thumbnail_data];
// Set image tint
UIColor *color = [UIColor colorWithRed:(160/15.0) green:(97/15.0) blue:(5/55.0) alpha:0.5];
myImage = [util tintedImageWithColor:(UIImage *) myImage:(UIColor *) color blendingMode:kCGBlendModeDestinationIn highQuality:YES];
cell.imageView.image = myImage;
return cell;
}
Utils.m
- (UIImage *)tintedImageWithColor:(UIImage*)image : (UIColor *)tintColor blendingMode:(CGBlendMode)blendMode highQuality:(BOOL) yerOrNo;
{
CGSize size = CGSizeMake(70, 70);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
if (yerOrNo) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
}
[tintColor setFill];
CGRect bounds = CGRectMake(0, 0, size.width, size.height);
UIRectFill(bounds);
[image drawInRect:bounds blendMode:blendMode alpha:1.0f];
if (blendMode != kCGBlendModeDestinationIn)
[image drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:0.4];
UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return tintedImage;
}
Utils.h
- (UIImage *)tintedImageWithColor:(UIImage*)image : (UIColor *)tintColor blendingMode:(CGBlendMode)blendMode highQuality:(BOOL) yerOrNo;
I've found this piece on code in one of my projects, I don't know the exact source, but it should works, use kCGBlendModeDestinationIn:
- (UIImage *)tintedImageWithColor:(UIColor *)tintColor blendingMode:(CGBlendMode)blendMode highQuality:(BOOL) yerOrNo;
{
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
if (yerOrNo) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
}
[tintColor setFill];
CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
UIRectFill(bounds);
[self drawInRect:bounds blendMode:blendMode alpha:1.0f];
if (blendMode != kCGBlendModeDestinationIn)
[self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0];
UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return tintedImage;
}
I found that the iPad twitter.app UITableViewCell's border have two pixel line, it looks beautiful and professional,how can I do that? Thank you!
Because UITableViewCell inherits from UIView, a cell has a content view. You can add your own subviews (the labels and textfields) to that contentView and lay them out programmatically or using the Interface Builder.
There are a lot of online tutorials on how to accomplish that. Just search with google for "uitableviewcell interface builder tutorial".
Check out this pretty good tutorial Custom UITableViewCell Using Interface Builder.
Finally,I customed UITableViewCell use code,and I think it looks well. : )
MenuViewController.m file:
- (id)initWithFrame:(CGRect)frame {
if (self = [super init]) {
[self.view setFrame:frame];
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) style:UITableViewStylePlain];
[_tableView setDelegate:self];
[_tableView setDataSource:self];
[_tableView setBackgroundColor:[UIColor clearColor]];
[_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
UIView* footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 1)];
[_tableView setTableFooterView:footerView];
[footerView release];
[self.view addSubview:_tableView];
}
return self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
DoubleSeparatorCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[DoubleSeparatorCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
NSString *text;
UIColor *upperLineColor,*lowerLineColor,*viewColor;
upperLineColor = RGBA(255, 255, 255, 30);
lowerLineColor = RGBA(0, 0, 0, 50);
viewColor = RGBA(0,0,0,5);
if ([indexPath row] == 0) {
text = NSLocalizedString(#"...", nil);
} else if ([indexPath row] == 1) {
text = NSLocalizedString(#"...", nil);
} else if ([indexPath row] == 2) {
text = NSLocalizedString(#"...", nil);
} else {
text = NSLocalizedString(#"...", nil);
}
[cell.textLabel setText:text];
[cell.textLabel setTextColor:RGBA(170, 170, 170, 100)];
[cell.textLabel setShadowColor:[UIColor blackColor]];
[cell.textLabel setShadowOffset:CGSizeMake(1, 1)];
[cell.upperLine setBackgroundColor:upperLineColor];
[cell.lowerLine setBackgroundColor:lowerLineColor];
[cell.contentView setBackgroundColor:viewColor];
return cell;
}
DoubleSeparatorCell.h
#interface DoubleSeparatorCell : UITableViewCell {
UIView *upperLine;
UIView *lowerLine;
}
#property (nonatomic ,retain) UIView *upperLine;
#property (nonatomic ,retain) UIView *lowerLine;
#end
DoubleSeparatorCell.m
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.upperLine = [[UIView alloc] init];
self.lowerLine = [[UIView alloc] init];
[self.contentView addSubview:self.upperLine];
[self.contentView addSubview:self.lowerLine];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.upperLine setFrame:CGRectMake(0, 0, self.contentView.frame.size.width, 1)];
[self.lowerLine setFrame:CGRectMake(0, self.contentView.frame.size.height - 1, self.frame.size.width, 1)];
}
Srikar has already showed you the right path. By the way i just want to add the following:
You can cutomize your cell programmatically which can be done by inheriting the native class UITableViewCell.
Then by, create the instance of table view cell class and add it to the UITableView.
Now the cell is yours.
Happy Coding,
Arun
I'd point out that those cells you screenshot appear to have a light gray top border of 1 point and a dark gray bottom border of 1 point (or maybe they are pixels - sorry my eyes aren't that good :-) ).
So it may be kind of a hack (go on, savage me people), but you could:
Create a UILabel topBorder with frame CGRect(0,0,cell.contentView.frame.size,width,1)
Create a UILabel bottomBorder with frame CGRect (0,cell.contentView.frame.size.height - 1,cell.contentView.frame.size.width,1)
Set color of topBorder to UIColor lightGrayColor (or tweak for exact colors)
Set color of bottomBorder to UIColor darkGrayColor (ditto)
Add both subViews to cell.contentView
Note that you do not have to subclass UITableCellView - simply add these steps to your tableView:cellForRowAtIndexPath: method and they will appear.
Enjoy,
Damien
Override drawRect method and draw lines as you need.
- (void)drawRect:(CGRect)rect
{
CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [topColor CGColor]);
// border top
CGContextMoveToPoint(context, bounds.origin.x, bounds.origin.y);
CGContextAddLineToPoint(context, bounds.origin.x+bounds.size.width, bounds.origin.y);
CGContextStrokePath(context);
// border bottom
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [lowerColor CGColor]);
CGContextMoveToPoint(context, bounds.origin.x, bounds.origin.y+1);
CGContextAddLineToPoint(context, bounds.origin.x+bounds.size.width, bounds.origin.y+1);
CGContextStrokePath(context);
}
I am looking to draw a UILabel (preferable through subclassing) as a transparent label, but with solid background. I draw up an quick example (sorry, it's ugly, but it gets the points across :)).
Basically I have a UILabel and I would like the background to be a set colour, and the text should be transparent. I do not want to colour the text with the views background, but instead have it be 100% transparent, since I have a texture in the background that I want to make sure lines up inside and outside of the label.
I've been spending the night browsing SO and searching on Google, but I have found no helpful sources. I don't have much experience with CG drawing, so I would appreciate any links, help, tutorial or sample code (maybe Apple has some I need to have a look at?).
Thanks a bunch!
I've rewritten it as a UILabel subclass using barely any code and posted it on GitHub
The gist of it is you override drawRect but call [super drawRect:rect] to let the UILabel render as normal. Using a white label color lets you easily use the label itself as a mask.
- (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);
}
Solved using CALayer masks. Creating a standard mask (wallpapered text, for example) is simple. To create the knocked-out text, I had to invert the alpha channel of my mask, which involved rendering a label to a CGImageRef and then doing some pixel-pushing.
Sample application is available here: https://github.com/robinsenior/RSMaskedLabel
Relevant code is here to avoid future link-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
Here's a technique that's similar to Matt Gallagher's, which will generate an inverted text mask with an image.
Allocate a (mutable) data buffer. Create a bitmap context with an 8-bit alpha channel. Configure settings for text drawing. Fill the whole buffer in copy mode (default colour assumed to have alpha value of 1). Write the text in clear mode (alpha value of 0). Create an image from the bitmap context. Use the bitmap as a mask to make a new image from the source image. Create a new UIImage and clean up.
Every time the textString or sourceImage or size values change, re-generate the final image.
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);
Another way to do this involves putting the textImage into a new layer and setting that layer on your view's layer. (Remove the lines that create "newImage" and "finalImage".) Assuming this happens inside your view's code somewhere:
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;
There are more alternatives, some might be better (subclass UIImage and draw the text directly in clear mode after the superclass has done its drawing?).
Swift 5 solution (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)
}
}
I've been trying to display a NSImage on a CALayer. Then I realised I need to convert it to a CGImage apparently, then display it...
I have this code which doesn't seem to be working
CALayer *layer = [CALayer layer];
NSImage *finderIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFinderIcon)];
[finderIcon setSize:(NSSize){ 128.0f, 128.0f }];
CGImageSourceRef source;
source = CGImageSourceCreateWithData((CFDataRef)finderIcon, NULL);
CGImageRef finalIcon = CGImageSourceCreateImageAtIndex(source, 0, NULL);
layer.bounds = CGRectMake(128.0f, 128.0f, 4, 4);
layer.position = CGPointMake(128.0f, 128.0f);
layer.contents = finalIcon;
// Insert the layer into the root layer
[mainLayer addSublayer:layer];
Why? How can I get this to work?
From the comments: Actually, if you're on 10.6, you can also just set the CALayer's contents to an NSImage rather than a CGImageRef...
If you're on OS X 10.6 or later, take a look at NSImage's CGImageForProposedRect:context:hints: method.
If you're not, I've got this in a category on NSImage:
-(CGImageRef)CGImage
{
CGContextRef bitmapCtx = CGBitmapContextCreate(NULL/*data - pass NULL to let CG allocate the memory*/,
[self size].width,
[self size].height,
8 /*bitsPerComponent*/,
0 /*bytesPerRow - CG will calculate it for you if it's allocating the data. This might get padded out a bit for better alignment*/,
[[NSColorSpace genericRGBColorSpace] CGColorSpace],
kCGBitmapByteOrder32Host|kCGImageAlphaPremultipliedFirst);
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:bitmapCtx flipped:NO]];
[self drawInRect:NSMakeRect(0,0, [self size].width, [self size].height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapCtx);
CGContextRelease(bitmapCtx);
return (CGImageRef)[(id)cgImage autorelease];
}
I think I wrote this myself. But it's entirely possible that I ripped it off from somewhere else like Stack Overflow. It's an older personal project and I don't really remember.
Here's some code which may help you - I sure hope the formatting of this does not get all messed up like it appears is going to happen - all I can offer is that this works for me.
// -------------------------------------------------------------------------------------
- (void)awakeFromNib
{
// setup our main window 'contentWindow' to use layers
[[contentWindow contentView] setWantsLayer:YES]; // NSWindow*
// create a root layer to contain all of our layers
CALayer *root = [[contentWindow contentView] layer];
// use constraint layout to allow sublayers to center themselves
root.layoutManager = [CAConstraintLayoutManager layoutManager];
// create a new layer which will contain ALL our sublayers
// -------------------------------------------------------
mContainer = [CALayer layer];
mContainer.bounds = root.bounds;
mContainer.frame = root.frame;
mContainer.position = CGPointMake(root.bounds.size.width * 0.5,
root.bounds.size.height * 0.5);
// insert layer on the bottom of the stack so it is behind the controls
[root insertSublayer:mContainer atIndex:0];
// make it resize when its superlayer does
root.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
// make it resize when its superlayer does
mContainer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
}
// -------------------------------------------------------------------------------------
- (void) loadMyImage:(NSString*) path
n:(NSInteger) num
x:(NSInteger) xpos
y:(NSInteger) ypos
h:(NSInteger) hgt
w:(NSInteger) wid
b:(NSString*) blendstr
{
#ifdef __DEBUG_LOGGING__
NSLog(#"loadMyImage - ENTER [%#] num[%d] x[%d] y[%d] h[%d] w[%d] b[%#]",
path, num, xpos, ypos, hgt, wid, blendstr);
#endif
NSInteger xoffset = ((wid / 2) + xpos); // use CORNER versus CENTER for location
NSInteger yoffset = ((hgt / 2) + ypos);
CIFilter* filter = nil;
CGRect cgrect = CGRectMake((CGFloat) xoffset, (CGFloat) yoffset,
(CGFloat) wid, (CGFloat) hgt);
if(nil != blendstr) // would be equivalent to #"CIMultiplyBlendMode" or similar
{
filter = [CIFilter filterWithName:blendstr];
}
// read image file via supplied path
NSImage* theimage = [[NSImage alloc] initWithContentsOfFile:path];
if(nil != theimage)
{
[self setMyImageLayer:[CALayer layer]]; // create layer
myImageLayer.frame = cgrect; // locate & size image
myImageLayer.compositingFilter = filter; // nil is OK if no filter
[myImageLayer setContents:(id) theimage]; // deposit image into layer
// add new layer into our main layer [see awakeFromNib above]
[mContainer insertSublayer:myImageLayer atIndex:0];
[theimage release];
}
else
{
NSLog(#"ERROR loadMyImage - no such image [%#]", path);
}
}
+ (CGImageRef) getCachedImage:(NSString *) imageName
{
NSGraphicsContext *context = [[NSGraphicsContext currentContext] graphicsPort];
NSImage *img = [NSImage imageNamed:imageName];
NSRect rect = NSMakeRect(0, 0, [img size].width, [img size].height);
return [img CGImageForProposedRect:&rect context:context hints:NULL];
}
+ (CGImageRef) getImage:(NSString *) imageName withExtension:(NSString *) extension
{
NSGraphicsContext *context = [[NSGraphicsContext currentContext] graphicsPort];
NSString* imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:extension];
NSImage* img = [[NSImage alloc] initWithContentsOfFile:imagePath];
NSRect rect = NSMakeRect(0, 0, [img size].width, [img size].height);
CGImageRef imgRef = [img CGImageForProposedRect:&rect context:context hints:NULL];
[img release];
return imgRef;
}
then you can set it:
yourLayer.contents = (id)[self getCachedImage:#"myImage.png"];
or
yourLayer.contents = (id)[self getImage:#"myImage" withExtension:#"png"];