Inner Shadow in UILabel - iphone

is it possible to create such a UILabel with inner and outer shadow?
alt text http://dl.getdropbox.com/u/80699/Bildschirmfoto%202010-07-12%20um%2021.28.57.png
i only know shadowColor and shadowOffset
zoomed:
alt text http://dl.getdropbox.com/u/80699/Bildschirmfoto%202010-07-12%20um%2021.39.56.png
thanks!

The answer by dmaclach is only suitable for shapes that can easily be inverted. My solution is a custom view that works with any shape and also text. It requires iOS 4 and is resolution independent.
First, a graphical explanation of what the code does. The shape here is a circle.
The code draws text with a white dropshadow. If it's not required, the code could be refactored further, because the dropshadow needs to be masked differently. If you need it on an older version of iOS, you would have to replace the block and use an (annoying) CGBitmapContext.
- (UIImage*)blackSquareOfSize:(CGSize)size {
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
[[UIColor blackColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, size.width, size.height));
UIImage *blackSquare = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return blackSquare;
}
- (CGImageRef)createMaskWithSize:(CGSize)size shape:(void (^)(void))block {
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
block();
CGImageRef shape = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
UIGraphicsEndImageContext();
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(shape),
CGImageGetHeight(shape),
CGImageGetBitsPerComponent(shape),
CGImageGetBitsPerPixel(shape),
CGImageGetBytesPerRow(shape),
CGImageGetDataProvider(shape), NULL, false);
return mask;
}
- (void)drawRect:(CGRect)rect {
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:40.0f];
CGSize fontSize = [text_ sizeWithFont:font];
CGImageRef mask = [self createMaskWithSize:rect.size shape:^{
[[UIColor blackColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
[[UIColor whiteColor] setFill];
// custom shape goes here
[text_ drawAtPoint:CGPointMake((self.bounds.size.width/2)-(fontSize.width/2), 0) withFont:font];
[text_ drawAtPoint:CGPointMake((self.bounds.size.width/2)-(fontSize.width/2), -1) withFont:font];
}];
CGImageRef cutoutRef = CGImageCreateWithMask([self blackSquareOfSize:rect.size].CGImage, mask);
CGImageRelease(mask);
UIImage *cutout = [UIImage imageWithCGImage:cutoutRef scale:[[UIScreen mainScreen] scale] orientation:UIImageOrientationUp];
CGImageRelease(cutoutRef);
CGImageRef shadedMask = [self createMaskWithSize:rect.size shape:^{
[[UIColor whiteColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(0, 1), 1.0f, [[UIColor colorWithWhite:0.0 alpha:0.5] CGColor]);
[cutout drawAtPoint:CGPointZero];
}];
// create negative image
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
[[UIColor blackColor] setFill];
// custom shape goes here
[text_ drawAtPoint:CGPointMake((self.bounds.size.width/2)-(fontSize.width/2), -1) withFont:font];
UIImage *negative = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef innerShadowRef = CGImageCreateWithMask(negative.CGImage, shadedMask);
CGImageRelease(shadedMask);
UIImage *innerShadow = [UIImage imageWithCGImage:innerShadowRef scale:[[UIScreen mainScreen] scale] orientation:UIImageOrientationUp];
CGImageRelease(innerShadowRef);
// draw actual image
[[UIColor whiteColor] setFill];
[text_ drawAtPoint:CGPointMake((self.bounds.size.width/2)-(fontSize.width/2), -0.5) withFont:font];
[[UIColor colorWithWhite:0.76 alpha:1.0] setFill];
[text_ drawAtPoint:CGPointMake((self.bounds.size.width/2)-(fontSize.width/2), -1) withFont:font];
// finally apply shadow
[innerShadow drawAtPoint:CGPointZero];
}

Although Steve's answer does work, it didn't scale to my particular case and so I ended up creating an inner shadow by applying a shadow to a CGPath after clipping my context:
CGContextRef context = UIGraphicsGetCurrentContext();
// clip context so shadow only shows on the inside
CGPathRef roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:4].CGPath;
CGContextAddPath(context, roundedRect);
CGContextClip(context);
CGContextAddPath(context, roundedRect);
CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(0, 0), 3, [UIColor colorWithWhite:0 alpha:1].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0 alpha:1].CGColor);
CGContextStrokePath(context);
Which results in:
You can change the shadow offset and blur to change the shadow style. If you need it to be darker, you can also successively add and then stroke the same CGPath, which will make the shadows pile up.

I use FXLabel for inner shadow, look here https://github.com/nicklockwood/FXLabel.

Yes, I have a blog post about it here. The short answer is to borrow the JTAInnerShadowLayer from here, subclass UILabel, and change its layerclass by overriding + (Class)layerClass so that it returns JTAInnerShadowLayer class like this:
+ (Class)layerClass
{
return [JTAInnerShadowLayer class];
}
Then in the init method for the layer set up the JTAInnerShadowLayer something like this:
[self setBackgroundColor:[UIColor clearColor]];
JTAInnerShadowLayer *innerShadow = (JTAInnerShadowLayer *)[self layer];
[innerShadow setClipForAnyAlpha:YES];
[innerShadow setOutsideShadowSize:CGSizeMake(0.0, 1.0) radius:1.0];
[innerShadow setInsideShadowSize:CGSizeMake (0.0, 4.0) radius:6.0];
/* Uncomment this to make the label also draw the text (won't work well
with black text!*/
//[innerShadow drawOriginalImage];
(or just borrow the JTAIndentLabel class).

You will have to do your own custom drawing to get an inner shadow. There's no way currently to do it with a standard UILabel.
Here's a reasonable explanation on doing inner shadow with Quartz.
Inner shadow in Core Graphics

Try PaintCode trial version! For beginners, you'll learn how to create custom drawings. It's very nice! :3 You can use it for practice or for learning, but don't use it all the time, you should also learn how to create drawings on your own.
Disclaimer: I'm not an endorser, I was just amazed by my discovery. ;)

Related

UILabel text color based on custom linear gradient

So I want to set the text color for a UILabel based on a gradient made in photoshop. I have the rgb values for the gradient, {211,119,95} and {199,86,56}. Is this possible? How can I do it?
Another way if you want to target iOS 6+, with a category to UIColor
You create a UIColor from a gradient:
+ (UIColor*) gradientFromColor:(UIColor*)c1 toColor:(UIColor*)c2 withHeight:(int)height
{
CGSize size = CGSizeMake(1, height);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
NSArray* colors = [NSArray arrayWithObjects:(id)c1.CGColor, (id)c2.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorspace, (CFArrayRef)colors, NULL);
CGContextDrawLinearGradient(context, gradient, CGPointMake(0, 0), CGPointMake(0, size.height), 0);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);
UIGraphicsEndImageContext();
return [UIColor colorWithPatternImage:image];
}
Then with attrString as your NSMutableAttributeString:
[attrString addAttribute:NSForegroundColorAttributeName value:[UIColor gradientFromColor:[UIColor greenColor] toColor:[UIColor redColor] withHeight:labelView.height] range:defaultRange];
labelView.attributedString = attrString;
or simply set the textColor if you don't also need strokes or other styling effect
labelView.textColor = [UIColor gradientFromColor:[UIColor greenColor] toColor:[UIColor redColor] withHeight:labelView.height];
And voila, it works better with UILabel over one line, otherwise you have to calculate your line height from your font (UIFont.leading) and pass it to the method, the background will repeat vertically.
You might want to use one of these customizable labels:
FXLabel
LEffectLabel
THLabel

Add drop shadow to text written on UIIImage

I'm writing text onto an image like so:
-(UIImage *)addLabelToImage:(UIImage *)img
{
UIFont *font = [UIFont boldSystemFontOfSize:20];
UIGraphicsBeginImageContext(img.size);
[img drawInRect:CGRectMake(0, 0,img.size.width,img.size.height)];
CGRect rect = CGRectMake(60, 550, img.size.width, img.size.height);
[[UIColor whiteColor] set];
[[NSString stringWithFormat:#"%#, %#", [[NSUserDefaults standardUserDefaults] valueForKey:#"whatever"], [[NSUserDefaults standardUserDefaults] valueForKey:#"whatever2"]] drawInRect:CGRectIntegral(rect) withFont:font];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
How would I add a drop shadow to this text? I know I can just redraw the text in black shifted over a few pixels, but is there an easier way?
Cheers,
George
Before you draw your text, you can set a shadow like this:
//...
CGContextRef c = UIGraphicsGetCurrentContext();
CGSize offset = CGSizeMake(0, 1);
CGFloat blur = 2.0;
UIColor *shadowColor = [UIColor blackColor];
CGContextSetShadowWithColor(c, offset, blur, [shadowColor CGColor]);
//draw your text ...
Adjust the offset, blur and shadowColor parameters as needed to achieve the effect you want. For a sharp shadow (like on a UILabel), set blur to 0.

erase a persistent drawRect

I at last managed to get DrawRect that did not clear all the time using
(I also have the setClearsContextBeforeDrawing:YES )
- (void)drawRect:(CGRect)rect
{
UIGraphicsPushContext(drawingContext);
CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
UIImage *uiImage = [[UIImage alloc] initWithCGImage:cgImage];
CGContextSetLineWidth(drawingContext, 4.0);
if (draw)
CGContextSetStrokeColorWithColor(drawingContext, [UIColor whiteColor] CGColor]);
else
CGContextSetStrokeColorWithColor(drawingContext, [[UIColor clearColor] CGColor]);
CGContextMoveToPoint(drawingContext, lastPt.x - (31.0 / self.transform.a), lastPt.y - (31.0 / self.transform.a) );
CGContextAddLineToPoint(drawingContext, currPt.x - (31.0 / self.transform.a), currPt.y - (31.0 / self.transform.a) );
CGContextStrokePath(drawingContext);
UIGraphicsPopContext();
CGImageRelease(cgImage);
[uiImage drawInRect: rect];
lastPt = currPt;
}
This leaves behind a line at it is drawn
My problem is when draw is NO [UIColor clearColor] is NOT erasing What am I supposed to use to erase ? Thanks in advance
Because clearColor has an alpha of zero, drawing with it normally doesn't have any effect. You need to set the context's blend mode to kCGBlendModeCopy to make it take effect.
However, it would be simpler to just set the context's blend mode to kCGBlendModeClear when you want to erase, and back to kCGBlendModeNormal when you want to draw:
if (draw) {
CGContextSetBlendMode(drawingContext, kCGBlendModeNormal);
CGContextSetStrokeColorWithColor(drawingContext, [UIColor whiteColor] CGColor]);
} else {
CGContextSetBlendMode(drawingContext, kCGBlendModeClear);
}

A UIImage created in coregraphics isn't repeating

I have this code, which should repeat the same UIImage:
UIView *paperMiddle = [[UIView alloc] initWithFrame:CGRectMake(0, 34, 320, rect.size.height - 34)];
UIImage *paperPattern = paperBackgroundPattern(context);
paperMiddle.backgroundColor = [UIColor colorWithPatternImage:paperPattern];
[self addSubview:paperMiddle];
And this is the paperBackgroundPattern method:
UIImage *paperBackgroundPattern(CGContextRef context) {
CGRect paper3 = CGRectMake(10, -15, 300, 16);
CGRect paper2 = CGRectMake(13, -15, 294, 16);
CGRect paper1 = CGRectMake(16, -15, 288, 16);
//Shadow
CGContextSetShadowWithColor(context, CGSizeMake(0,0), 10, [[UIColor colorWithWhite:0 alpha:0.5]CGColor]);
CGPathRef path = createRoundedRectForRect(paper3, 0);
CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextAddPath(context, path);
CGContextFillPath(context);
//Layers of paper
CGContextSaveGState(context);
drawPaper(context, paper3);
drawPaper(context, paper2);
drawPaper(context, paper1);
CGContextRestoreGState(context);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(320, 1), NO, 0);
UIImage *paperImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return paperImage;
}
It isn't repeating the image. A result of having the image there though is that it shows as the top pixel of the screen (which isn't the frame i've given it).
Any ideas why?
I don't know what the context is that you're passing in, but whatever it is, you shouldn't be drawing into it. And you aren't drawing anything into the context that you made with UIGraphicsBeginImageContextWithOptions.
If you want to generate an image, you don't need to pass in a context, just use the one that UIGraphicsBeginImageContextWithOptions makes for you.
UIImage *paperBackgroundPattern() {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(320, 1), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// draw into context, then...
UIImage *paperImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return paperImage;
}
Also -- are you really trying to make an image that's 320 pt wide and 1 high? It seems odd that you are drawing such elaborate stuff into such a tiny image.

iOS: Draw NSString and border on UIImage

I am wanting to draw an NSString and a border onto a UIImage that I already have. I found a method that will draw an NSString as a UIImage, but I need it to draw on an image that I provide.
-(UIImage *)imageFromText:(NSString *)text
{
// set the font type and size
UIFont *font = [UIFont systemFontOfSize:20.0];
CGSize size = [text sizeWithFont:font];
// check if UIGraphicsBeginImageContextWithOptions is available (iOS is 4.0+)
if (UIGraphicsBeginImageContextWithOptions != NULL)
UIGraphicsBeginImageContextWithOptions(size,NO,0.0);
else
// iOS is < 4.0
UIGraphicsBeginImageContext(size);
// optional: add a shadow, to avoid clipping the shadow you should make the context size bigger
//
// CGContextRef ctx = UIGraphicsGetCurrentContext();
// CGContextSetShadowWithColor(ctx, CGSizeMake(1.0, 1.0), 5.0, [[UIColor grayColor] CGColor]);
// draw in context, you can use also drawInRect:withFont:
[text drawAtPoint:CGPointMake(0.0, 0.0) withFont:font];
// transfer image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
How would I modify this method to provide my own background image, as well as adding a border?
If you are displaying the UIImage in a UIImageView you can set the UIImageView.layer.delegate and use something like:
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextSetFillColorWithColor(ctx, [[UIColor darkTextColor] CGColor]);
UIGraphicsPushContext(ctx);
[word drawAtPoint:CGPointMake(30.0f, 30.0f)
forWidth:200.0f
withFont:[UIFont boldSystemFontOfSize:32]
lineBreakMode:UILineBreakModeClip];
UIGraphicsPopContext();
}
Code from Add text to CALayer
The border is easy, just use the CALayer properties:
imageview.layer.borderColor = [UIColor blackColor].CGColor;
imageview.sublayer.borderWidth = 2.0;
You are looking for CALayers.
Here is very good tutorial how to create and use them.
So basically you will add new CALayer with image as a background and then draw on it text.
http://www.raywenderlich.com/2502/introduction-to-calayers-tutorial
Use this function to Draw NSString and border on UIImage
For border check CGContextSetRGBStrokeColor
-(UIImage *)imageFromText:(NSString *)text
{
// set the font type and size
UIFont *font = [UIFont systemFontOfSize:20.0];
CGSize size = [text sizeWithFont:font];
// check if UIGraphicsBeginImageContextWithOptions is available (iOS is 4.0+)
if (UIGraphicsBeginImageContextWithOptions != NULL)
UIGraphicsBeginImageContextWithOptions(size,NO,0.0);
else
// iOS is < 4.0
UIGraphicsBeginImageContext(size);
// optional: add a shadow, to avoid clipping the shadow you should make the context size bigger
//
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(ctx, CGSizeMake(1.0, 1.0), 5.0, [[UIColor brownColor] CGColor]);
// draw in context, you can use also drawInRect:withFont:
[text drawAtPoint:CGPointMake(0.0, 0.0) withFont:font];
//CGImageRef cimg = UIGraphicsGetCurrentContext();
// transfer image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
[image drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
//CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 2.0, 3.5, 5.0, 1.0);
CGContextStrokeRect(ctx, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}