Creating filled paths in Core Graphics is straight-forward, as is creating filled text. But I am yet to find examples of paths filled EXCEPT for text in a sub-path. My experiments with text drawing modes, clipping etc have got me nowhere.
Here's an example (created in photoshop). How would you go about creating the foreground shape in Core Graphics?
I would mention that this technique appears to be used heavily in an upcoming version of a major mobile OS, but I don't want to fall afoul of SO's NDA-police ;)
Here's some code I ran and tested that will work for you. See the inline comments for details:
Update: I've removed the manualYOffset:
parameter. It now does a calculation to center the text vertically in the circle. Enjoy!
- (void)drawRect:(CGRect)rect {
// Make sure the UIView's background is set to clear either in code or in a storyboard/nib
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] setFill];
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect), CGRectGetWidth(rect)/2, 0, 2*M_PI, YES);
CGContextFillPath(context);
// Manual offset may need to be adjusted depending on the length of the text
[self drawSubtractedText:@"Foo" inRect:rect inContext:context];
}
- (void)drawSubtractedText:(NSString *)text inRect:(CGRect)rect inContext:(CGContextRef)context {
// Save context state to not affect other drawing operations
CGContextSaveGState(context);
// Magic blend mode
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
// This seemingly random value adjusts the text
// vertically so that it is centered in the circle.
CGFloat Y_OFFSET = -2 * (float)[text length] + 5;
// Context translation for label
CGFloat LABEL_SIDE = CGRectGetWidth(rect);
CGContextTranslateCTM(context, 0, CGRectGetHeight(rect)/2-LABEL_SIDE/2+Y_OFFSET);
// Label to center and adjust font automatically
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, LABEL_SIDE, LABEL_SIDE)];
label.font = [UIFont boldSystemFontOfSize:120];
label.adjustsFontSizeToFitWidth = YES;
label.text = text;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[label.layer drawInContext:context];
// Restore the state of other drawing operations
CGContextRestoreGState(context);
}
Here's the result (you can change the background to anything and you'll still be able to see through the text):
Below is a UIView
subclass that will do what you want. It will correctly size and position 1 or more letters in the circle. Here's how it looks with 1-3 letters at various sizes (32, 64, 128, 256):
With the availability of user defined runtime attributes in Interface Builder, you can even configure the view from within IB. Just set the text
property as a runtime attribute and the backgroundColor
to the color you want for the circle.
Here's the code:
@interface MELetterCircleView : UIView
/**
* The text to display in the view. This should be limited to
* just a few characters.
*/
@property (nonatomic, strong) NSString *text;
@end
@interface MELetterCircleView ()
@property (nonatomic, strong) UIColor *circleColor;
@end
@implementation MELetterCircleView
- (instancetype)initWithFrame:(CGRect)frame text:(NSString *)text
{
NSParameterAssert(text);
self = [super initWithFrame:frame];
if (self)
{
self.text = text;
}
return self;
}
// Override to set the circle's background color.
// The view's background will always be clear.
-(void)setBackgroundColor:(UIColor *)backgroundColor
{
self.circleColor = backgroundColor;
[super setBackgroundColor:[UIColor clearColor]];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self.circleColor setFill];
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect),
CGRectGetWidth(rect)/2, 0, 2*M_PI, YES);
CGContextFillPath(context);
[self drawSubtractedText:self.text inRect:rect inContext:context];
}
- (void)drawSubtractedText:(NSString *)text inRect:(CGRect)rect
inContext:(CGContextRef)context
{
CGContextSaveGState(context);
// Magic blend mode
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
CGFloat pointSize =
[self optimumFontSizeForFont:[UIFont boldSystemFontOfSize:100.f]
inRect:rect
withText:text];
UIFont *font = [UIFont boldSystemFontOfSize:pointSize];
// Move drawing start point for centering label.
CGContextTranslateCTM(context, 0,
(CGRectGetMidY(rect) - (font.lineHeight/2)));
CGRect frame = CGRectMake(0, 0, CGRectGetWidth(rect), font.lineHeight)];
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.font = font;
label.text = text;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[label.layer drawInContext:context];
// Restore the state of other drawing operations
CGContextRestoreGState(context);
}
-(CGFloat)optimumFontSizeForFont:(UIFont *)font inRect:(CGRect)rect
withText:(NSString *)text
{
// For current font point size, calculate points per pixel
CGFloat pointsPerPixel = font.lineHeight / font.pointSize;
// Scale up point size for the height of the label.
// This represents the optimum size of a single letter.
CGFloat desiredPointSize = rect.size.height * pointsPerPixel;
if ([text length] == 1)
{
// In the case of a single letter, we need to scale back a bit
// to take into account the circle curve.
// We could calculate the inner square of the circle,
// but this is a good approximation.
desiredPointSize = .80*desiredPointSize;
}
else
{
// More than a single letter. Let's make room for more.
desiredPointSize = desiredPointSize / [text length];
}
return desiredPointSize;
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With