iOS - How to achieve emboss effect for the text on UILabel?

emboss, iphone, shadow, uilabel

Solution

UPDATE FOR iOS 7.0

In iOS 7.0, Apple added a new attribute, `NSTextEffectAttributeName`, for attributed strings. If your deployment target is iOS 7.0 or later, you can set this attribute to `NSTextEffectLetterpressStyle` to draw an attributed string in an embossed style.

ORIGINAL

I can't say for certain how Apple draws the embossed text. It looks to me like they fill the string glyphs with a reddish color, then apply a shadow around the interior edges of the glyphs, and also apply a very faint shadow along the top outside edges of the glyphs. I tried it out and here's what it looks like:

On top is my rendering. Below that is a simple UILabel with shadow as Chris suggested in his answer. I put a screen shot of the Reminders app in the background.

Here's my code.

First, you need a function that creates an image mask of your string. You'll use the mask to draw the string itself, and then to draw a shadow that only appears around the inside edges of the string. This image just has an alpha channel and no RGB channels.

- (UIImage *)maskWithString:(NSString *)string font:(UIFont *)font size:(CGSize)size
{
    CGRect rect = { CGPointZero, size };
    CGFloat scale = [UIScreen mainScreen].scale;
    CGColorSpaceRef grayscale = CGColorSpaceCreateDeviceGray();
    CGContextRef gc = CGBitmapContextCreate(NULL, size.width * scale, size.height * scale, 8, size.width * scale, grayscale, kCGImageAlphaOnly);
    CGContextScaleCTM(gc, scale, scale);
    CGColorSpaceRelease(grayscale);
    UIGraphicsPushContext(gc); {
        [[UIColor whiteColor] setFill];
        [string drawInRect:rect withFont:font];
    } UIGraphicsPopContext();

    CGImageRef cgImage = CGBitmapContextCreateImage(gc);
    CGContextRelease(gc);
    UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationDownMirrored];
    CGImageRelease(cgImage);

    return image;
}

Second, you need a function that inverts that mask. You'll use this to make CoreGraphics draw a shadow around the inside edges of the string. This needs to be a full RGBA image. (iOS doesn't seem to support grayscale+alpha images.)

- (UIImage *)invertedMaskWithMask:(UIImage *)mask
{
    CGRect rect = { CGPointZero, mask.size };
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, mask.scale); {
        [[UIColor blackColor] setFill];
        UIRectFill(rect);
        CGContextClipToMask(UIGraphicsGetCurrentContext(), rect, mask.CGImage);
        CGContextClearRect(UIGraphicsGetCurrentContext(), rect);
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

You can use those in a function that draws the string in red and applies a shadow to its interior edges.

-(UIImage *)imageWithInteriorShadowAndString:(NSString *)string font:(UIFont *)font textColor:(UIColor *)textColor size:(CGSize)size
{
    CGRect rect = { CGPointZero, size };
    UIImage *mask = [self maskWithString:string font:font size:rect.size];
    UIImage *invertedMask = [self invertedMaskWithMask:mask];
    UIImage *image;
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale); {
        CGContextRef gc = UIGraphicsGetCurrentContext();
        // Clip to the mask that only allows drawing inside the string's image.
        CGContextClipToMask(gc, rect, mask.CGImage);
        // We apply the mask twice because we're going to draw through it twice.
        // Only applying it once would make the edges too sharp.
        CGContextClipToMask(gc, rect, mask.CGImage);
        mask = nil; // done with mask; let ARC free it

        // Draw the red text.
        [textColor setFill];
        CGContextFillRect(gc, rect);

        // Draw the interior shadow.
        CGContextSetShadowWithColor(gc, CGSizeZero, 1.6, [UIColor colorWithWhite:.3 alpha:1].CGColor);
        [invertedMask drawAtPoint:CGPointZero];
        invertedMask = nil; // done with invertedMask; let ARC free it

        image = UIGraphicsGetImageFromCurrentImageContext();
    }
    UIGraphicsEndImageContext();
    return image;
}

Next you need a function that takes an image and returns a copy with a faint upward shadow.

- (UIImage *)imageWithUpwardShadowAndImage:(UIImage *)image
{
    UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); {
        CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(0, -1), 1, [UIColor colorWithWhite:0 alpha:.15].CGColor);
        [image drawAtPoint:CGPointZero];
    }
    UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resultImage;
}

Finally, you can combine those functions to create an embossed image of your string. I put my final image into a `UIImageView` for easy testing.

- (void)viewDidLoad
{
    [super viewDidLoad];

    CGRect rect = self.imageView.bounds;
    NSString *string = @"Reminders";
    UIFont *font = [UIFont systemFontOfSize:33];
    UIImage *interiorShadowImage = [self imageWithInteriorShadowAndString:string
        font:font
        textColor:[UIColor colorWithHue:0 saturation:.9 brightness:.7 alpha:1]
        size:rect.size];
    UIImage *finalImage = [self imageWithUpwardShadowAndImage:interiorShadowImage];

    self.imageView.image = finalImage;
}

Problem

Can I know how to have the emboss effect as the text "Reminders" as shown on the picture? It looks like the text are embedded? Thanks

Original source