Drawing a path with subtracted text using Core Graphics

Creating filled paths in Core Graphics is straightforward, just like creating filled text. But I have not yet found examples of paths filled with EXCEPT for text in a subpath. My experiments with text drawing modes, cropping, etc. They didn’t lead to anything.

Here is an example (created in Photoshop). How are you going to create a foreground shape in Core Graphics?

Example of text subtracted from path (created in photoshop)

I would mention that this method is apparently heavily used in the upcoming version of the main mobile OS, but I don't want to fall for SO NDA police;)

+7
ios core-graphics drawing clipping
source share
2 answers

Here is the code that I ran and tested that will work for you. See comments in more details:

Update: I removed the manualYOffset: parameter. Now it calculates to center the text vertically in the circle. Enjoy it!

 - (void)drawRect:(CGRect)rect { // Make sure the UIView 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 can still see the text):

Result

+11
source share

Below is a subclass of UIView that will do what you want. It will correctly size and position 1 or more letters in a circle. Here's what it looks like with 1-3 letters of different sizes (32, 64, 128, 256):

Screenshothot

With custom runtime attributes in Interface Builder, you can even customize the view from within IB. Just set the text property as a run-time attribute and backgroundColor for the color you want for the circle.

User Defined Runtime Attributes

Here is 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 background color. // The view 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 make room for more. desiredPointSize = desiredPointSize / [text length]; } return desiredPointSize; } @end 
+3
source share

All Articles