18

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!

choise
  • 24,636
  • 19
  • 75
  • 131
  • I also wanted to know this to simulate a UITextField quite some time ago and did it using Quartz (as suggested) but there are some performance problems, I am quite sure that animating the bounds of your UILabel rendering shadows that way won't look good. It will look sluggish. In my case I ended up by using a stretched UIImage: stretchableImageWithLeftCapWidth:topCapHeight: it's much faster if it fit your needs – nacho4d Oct 09 '10 at 06:48
  • 3
    Image links in your question are broken – jjxtra Jun 19 '12 at 21:36

6 Answers6

126

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. alt text

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];
}
Steven XM
  • 1,461
  • 1
  • 10
  • 5
  • Wow, that was really helpful! Don't know why there were zero votes... Unfortunately, cannot set more than +1 for the solution. :) – Aleks N. Nov 29 '10 at 17:00
  • 6
    I created some functions to make using this approach even easier vailable on GitHub: https://github.com/mruegenberg/objc-utils/tree/master/UIKitAdditions. See the InnerShadowDrawing.h/.m files. – mrueg Mar 18 '11 at 11:20
  • How would I add this as a category method to UILabel? For instance I'd like to call something like: [UILabel innerShadow:1.0]; – Ryan Poolos Apr 27 '12 at 13:57
  • Can't edit comments yet - mrueg's code can be found at https://github.com/mruegenberg/objc-utils/tree/master/UIKit/Drawing – Oded Ben Dov Mar 12 '13 at 08:48
  • Also - Custom control can be found here: http://www.cocoacontrols.com/controls/fxlabel – Oded Ben Dov Mar 12 '13 at 10:03
  • You are a genius bro. – Seifolahi Dec 06 '16 at 07:42
13

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:

enter image description here

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.

samvermette
  • 40,269
  • 27
  • 112
  • 144
  • Hey Sam, this is great and works well for me, but do you know how to get this same effect without the border? Or maybe just a border that is about at half the opacity as this one? – Stephen Paul Nov 11 '15 at 19:22
7

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

Alexey Golikov
  • 652
  • 8
  • 19
2

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).

James Snook
  • 4,063
  • 2
  • 18
  • 30
0

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. ;)

KarenAnne
  • 2,764
  • 1
  • 28
  • 21
0

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

Community
  • 1
  • 1
dmaclach
  • 3,403
  • 1
  • 21
  • 23