Drawing filled circles with letters in iOS 7 - ios

The new ios 7 phone app has a favorites section. In that section the names of the contact appear next to a filled in circle with the inital of the contact inside the circle.
How is this drawn? With drawrect or is there already and object created for this?

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

Related

how Draw a percentage of a circle objective c

i can need layout like same as image
but i can not draw like this so any idea about this,
The easiest approach would be to have a view with two subviews, one for the green "percent filled" level, and one for the label for the text. Then you can update the frame for the "percent filled" based upon, obviously, what percent filled you want it. And then apply a circular mask to the whole thing.
For example:
// CircleLevelView.h
//
// Created by Robert Ryan on 10/28/17.
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface CircleLevelView : UIView
/// Percent filled
///
/// Value between 0.0 and 1.0.
#property (nonatomic) IBInspectable CGFloat percent;
/// Text to show up in center of view
///
/// Value between 0.0 and 1.0.
#property (nonatomic, strong) IBInspectable NSString *text;
#end
And
// CircleLevelView.m
//
// Created by Robert Ryan on 10/28/17.
#import "CircleLevelView.h"
#interface CircleLevelView()
#property (nonatomic, weak) CAShapeLayer *circleMask;
#property (nonatomic, weak) UILabel *label;
#property (nonatomic, weak) UIView *fillView;
#end
#implementation CircleLevelView
#synthesize percent = _percent;
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
[self configure];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
[self configure];
return self;
}
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (void)configure {
self.clipsToBounds = true;
UILabel *fillView = [[UILabel alloc] init];
fillView.translatesAutoresizingMaskIntoConstraints = false;
fillView.backgroundColor = [UIColor colorWithRed:169.0 / 255.0
green:208.0 / 255.0
blue:66.0 / 255.0
alpha:1.0];
[self addSubview:fillView];
self.fillView = fillView;
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = false;
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor blackColor];
label.textAlignment = NSTextAlignmentCenter;
[self addSubview:label];
self.label = label;
[NSLayoutConstraint activateConstraints:#[
[label.topAnchor constraintEqualToAnchor:self.topAnchor],
[label.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[label.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[fillView.topAnchor constraintEqualToAnchor:self.topAnchor],
[fillView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[fillView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[fillView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
]];
CAShapeLayer *circleMask = [CAShapeLayer layer];
circleMask.fillColor = [UIColor whiteColor].CGColor;
circleMask.strokeColor = [UIColor blackColor].CGColor;
circleMask.lineWidth = 0;
self.layer.mask = circleMask;
self.circleMask = circleMask;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGPoint center = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2.0, self.bounds.origin.y + self.bounds.size.height / 2.0);
CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) / 2.0;
self.circleMask.path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI * 2.0 clockwise:true].CGPath;
[self updatePercentFill];
}
- (void)updatePercentFill {
CGFloat circleHeight = MIN(self.bounds.size.width, self.bounds.size.height);
CGFloat percentHeight = circleHeight * self.percent;
self.fillView.frame = CGRectMake(0,
(self.bounds.size.height - circleHeight) / 2 + (circleHeight - percentHeight),
self.bounds.size.width,
circleHeight);
}
// MARK: - Custom Accessor Methods
- (CGFloat)percent {
return _percent;
}
- (void)setPercent:(CGFloat)percent {
_percent = percent;
[self updatePercentFill];
}
- (NSString *)text {
return self.label.text;
}
- (void)setText:(NSString *)text {
self.label.text = text;
}
#end
That yields:
If you know the chord position you can fill to the chord by setting the clip region and then filling the circle.
To work out the position of the chord to give an area of x% you'll need to do some geometry/trigonometry. Every chord which does not pass through the centre forms and isosceles triangle with the centre, and the two sides of that triangle which are radii delimit a segment of the circle. So the area on one side of a chord which does not pass through the centre is the difference of the area of a triangle and a segment, and you can work out how the chord divides the area or where the chord needs to be to divide the area in a particular ratio.
If that all sounds like gobbledegook try looking up chord geometry and you'll undoubtedly find books/sites with helpful diagrams and formulas.
HTH

How to resize text to label size?

I want to stretch the text inside a UILabel so that if fits exactly into the label (both width and height). I don't want to resize the UILabel in any way.
So far i used this: How to render stretched text in iOS? , but the text doesn't stretch 100% ( sometimes it exceeds the boundaries, and sometimes it leaves spacing on the margins ).
Is there another (preferably easier) way to do this?
This is what i was talking about: http://i.imgur.com/AMvfhsA.png . I get spacing on the left and the text exceeds boundaries on the right and also on the bottom edge.
This is the custom label class:
#import "CustomUILabel.h"
#implementation CustomUILabel
- (id)initWithFrame:(CGRect)frame text:(NSString*)text
{
self = [super initWithFrame:frame];
if (self) {
self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
self.text = text;
}
return self;
}
- (void)drawTextInRect:(CGRect)rect {
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}
- (void)drawRect:(CGRect)rect
{
[self drawScaledString:self.text];
}
- (void)drawScaledString:(NSString *)string
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
NSAttributedString *attrString = [self generateAttributedString:string];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attrString, CFRangeMake(0, string.length),
kCTForegroundColorAttributeName, self.textColor.CGColor);
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef) attrString);
// CTLineGetTypographicBounds doesn't give correct values,
// using GetImageBounds instead
CGRect imageBounds = CTLineGetImageBounds(line, context);
CGFloat width = imageBounds.size.width;
CGFloat height = imageBounds.size.height;
CGFloat padding = 0;
width += padding;
height += padding;
float sx = self.bounds.size.width / width;
float sy = self.bounds.size.height / height;
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 1, self.bounds.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextScaleCTM(context, sx, sy);
CGContextSetTextPosition(context, -imageBounds.origin.x + padding/2, -imageBounds.origin.y + padding/2);
CTLineDraw(line, context);
CFRelease(line);
}
- (NSAttributedString *)generateAttributedString:(NSString *)string
{
CTFontRef helv = CTFontCreateWithName(CFSTR("Helvetica-Bold"),20, NULL);
CGColorRef color = [UIColor blackColor].CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)helv, (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
nil];
NSAttributedString *attrString = [[NSMutableAttributedString alloc]
initWithString:string
attributes:attributesDict];
return attrString;
}
#end
And this is how i use it (I've added the label from storyboards):
#property (weak, nonatomic) IBOutlet CustomUILabel *label;
...
self.label.backgroundColor = [UIColor redColor];
self.label.text = #"OOOOO";
What you're asking boils down to calculating the exact bounding box of rendered text. When you have this box you can adjust the CTM to make the text fill the desired area.
Yet: There does not seem to be an easy way to do this. A lot of issues contribute to this problem:
Font metrics are a pretty complex topic. A rendered character (a glyph) has several bounding boxes depending on the intention.
In fonts, glyphs are often represented using bezier curves which makes calculating an exact bounding box difficult.
Attributes might influence graphical appearance in unforeseeable ways. AppKit/UIKit for example know a shadow attribute that can extend the area in which the font renders pixels.
There are more issues but the ones I listed might be enough to show that the task of exactly filling a box with a rendered text is not so easy.
Maybe there's another way of doing what you have in mind.

Drawing a path with subtracted text using Core Graphics

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

Unwanted Vertical Padding from iOS 6 on CATextLayer

Background: I started my project in iOS 5 and built out a beautiful button with layer. I added a textLayer onto the button and center it using the following code:
float textLayerVerticlePadding = ((self.bounds.size.height - fontSize) /2);
textLayer = [[CATextLayer alloc]init];
[textLayer setFrame:CGRectOffset(self.bounds, 0, textLayerVerticlePadding)];
It works great and looks dead center until iOS 6.
Problem: iOS 6 added a space (padding) between the topmost bound and the text in textLayer. This upsets the calculation above. Is there a way to make sure that iOS 6 does not? because I would like to support both iOS 5 and 6 (for those who prefers Google Map).
Pictures:
This one is iOS 5 and the red color is the background of the textLayer (to make it more apparent)
And this one is iOS 6
Update: While im sure all the answers below are correct in their own ways, I found the post by t0rst simplest way to execute this. HelveticaNeue leaves a little space for both iOS5 and iOS6, unlike Helvetica which leaves no space on the top in iOS5 and little space in iOS6.
Update 2: Played around with it a little more, and found out the size of the little space. Without going into detail, the space is 1/6 of your font size. So to compensate for it I wrote
float textLayerVerticlePadding = ((self.bounds.size.height - fontSize) /2) - (fontSize/6);
[textLayer setFrame:CGRectOffset(self.bounds, 0, textLayerVerticlePadding)];
With that code, I get a dead center every time. Note that this is only tested with HelveticaNeue-Bold on iOS5 and iOS6. I cannot say for anything else.
In iOS 5 and before, the first baseline in a CATextLayer is always positioned down from the top of the bounds by the ascent obtained from CTLineGetTypographicBounds when passed a CTLine made with the string for the first line.
In iOS 6, this doesn't hold true for all fonts anymore. Hence, when you are positioning a CATextLayer you can no longer reliably decide where to put it to get the right visual alignment. Or can you? ...
First, an aside: when trying to work out CATextLayer's positioning behaviour a while ago in iOS 5, I tried using all combinations of cap height, ascender from UIFont, etc. before finally discovering that ascent from CTLineGetTypographicBounds was the one I needed. In the process, I discovered that a) the ascent from UIFont ascender, CTFontGetAscent and CTLineGetTypographicBounds are inconsistent for certain typefaces, and b) the ascent is frequently strange - either cropping the accents or leaving way to much space above. The solution to a) is to know which value to use. There isn't really a solution to b) other than to leave plenty of room above by offsetting CATextLayer bounds if it likely you will have accents that get clipped.
Back to iOS 6. If you avoid the worst offending typefaces (as of 6.0, and probably subject to change), you can still do programatic positioning of CATextLayer with the rest of the typefaces. The offenders are: AcademyEngravedLetPlain, Courier, HoeflerText and Palatino - visually, these families position correctly (i.e. without clipping) in CATextLayer, but none of the three ascent sources gives you a usable indication of where the baseline is placed. Helvetica and .HelveticaNeueUI (aka system font) families position correctly with baseline at the ascent given by UIFont ascender, but the other ascent sources are not of use.
Some examples from tests I did. The sample text is drawn three times in different colours. The coordinate origin is top left of grey box. Black text is drawn by CTLineDraw offset downwards by the ascent from CTLineGetTypographicBounds; transparent red is drawn by CATextLayer with bounds equal to the grey box; transparent blue is drawn with the UIKit NSString addition drawAtPoint:withFont: locating at the origin of the grey box and with the UIFont.
1) A well behaved font, Copperplate-Light. The three samples are coincident, giving maroon, and meaning that the ascents are near enough the same from all sources. Same for iOS 5 and 6.
2) Courier under iOS 5. CATextLayer positions text too high (red), but CTLineDraw with ascent from CTLineGetTypographicBounds (black) matches CATextLayer positioning - so we can place and correct from there. NSString drawAtPoint:withFont: (blue) places the text without clipping. (Helvetica and .HelveticaNeueUI behave like this in iOS 6)
3) Courier under iOS 6. CATextLayer (red) now places the text so that it is not clipped, but the positioning no longer matches the ascent from CTLineGetTypographicBounds (black) or from UIFont ascender used in NSString drawAtPoint:withFont: (blue). This is unusable for programatic positioning. (AcademyEngravedLetPlain, HoeflerText and Palatino also behave like this in iOS 6)
Hope this helps avoid some of the hours of wasted time I went through, and if you want to dip in a bit deeper, have a play with this:
- (NSString*)reportInconsistentFontAscents
{
NSMutableString* results;
NSMutableArray* fontNameArray;
CGFloat fontSize = 28;
NSString* fn;
NSString* sample = #"Éa3Çy";
CFRange range;
NSMutableAttributedString* mas;
UIFont* uifont;
CTFontRef ctfont;
CTLineRef ctline;
CGFloat uif_ascent;
CGFloat ctfont_ascent;
CGFloat ctline_ascent;
results = [NSMutableString stringWithCapacity: 10000];
mas = [[NSMutableAttributedString alloc] initWithString: sample];
range.location = 0, range.length = [sample length];
fontNameArray = [NSMutableArray arrayWithCapacity: 250];
for (fn in [UIFont familyNames])
[fontNameArray addObjectsFromArray: [UIFont fontNamesForFamilyName: fn]];
[fontNameArray sortUsingSelector: #selector(localizedCaseInsensitiveCompare:)];
[fontNameArray addObject: [UIFont systemFontOfSize: fontSize].fontName];
[fontNameArray addObject: [UIFont italicSystemFontOfSize: fontSize].fontName];
[fontNameArray addObject: [UIFont boldSystemFontOfSize: fontSize].fontName];
[results appendString: #"Font name\tUIFA\tCTFA\tCTLA"];
for (fn in fontNameArray)
{
uifont = [UIFont fontWithName: fn size: fontSize];
uif_ascent = uifont.ascender;
ctfont = CTFontCreateWithName((CFStringRef)fn, fontSize, NULL);
ctfont_ascent = CTFontGetAscent(ctfont);
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)mas, range, kCTFontAttributeName, ctfont);
ctline = CTLineCreateWithAttributedString((CFAttributedStringRef)mas);
ctline_ascent = 0;
CTLineGetTypographicBounds(ctline, &ctline_ascent, 0, 0);
[results appendFormat: #"\n%#\t%.3f\t%.3f\t%.3f", fn, uif_ascent, ctfont_ascent, ctline_ascent];
if (fabsf(uif_ascent - ctfont_ascent) >= .5f // >.5 can round to pixel diffs in display
|| fabsf(uif_ascent - ctline_ascent) >= .5f)
[results appendString: #"\t*****"];
CFRelease(ctline);
CFRelease(ctfont);
}
[mas release];
return results;
}
t0rst's answer helps me.
I think capHeight and xHeight are key.
CATextLayer *mytextLayer = [CATextLayer layer];
CGFloat fontSize = 30;
UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
mytextLayer.font = (__bridge CFTypeRef)(boldFont.fontName);
mytextLayer.fontSize = fontSize;
CGFloat offsetY = 0;
//if system version is grater than 6
if(([[[UIDevice currentDevice] systemVersion] compare:#"6" options:NSNumericSearch] == NSOrderedDescending)){
offsetY = -(boldFont.capHeight - boldFont.xHeight);
}
//you have to set textX, textY, textWidth
mytextLayer.frame = CGRectMake(textX, textY + offsetY, textWidth, fontSize);
Wile I am waiting for an ultimate solution, I studied about RTLabel and TTTAttributedLabel, and made a simple class to draw text on a CALayer as Steve suggested. Hope it helps, and please don't hesitant to point out any mistake I have made.
CustomTextLayer.h
#import <QuartzCore/QuartzCore.h>
#interface CustomTextLayer : CALayer {
NSString *_text;
UIColor *_textColor;
NSString *_font;
float _fontSize;
UIColor *_strokeColor;
float _strokeWidth;
CTTextAlignment _textAlignment;
int _lineBreakMode;
float _suggestHeight;
}
-(float) suggestedHeightForWidth:(float) width;
#property (nonatomic, retain) NSString *text;
#property (nonatomic, retain) UIColor *textColor;
#property (nonatomic, retain) NSString *font;
#property (nonatomic, assign) float fontSize;
#property (nonatomic, retain) UIColor *strokeColor;
#property (nonatomic, assign) float strokeWidth;
#property (nonatomic, assign) CTTextAlignment textAlignment;
#end
CustomTextLayer.m
#import <CoreText/CoreText.h>
#import "CustomTextLayer.h"
#implementation CustomTextLayer
#synthesize text = _text, textColor = _textColor;
#synthesize font = _font, fontSize = _fontSize;
#synthesize strokeColor = _strokeColor, strokeWidth = _strokeWidth;
#synthesize textAlignment = _textAlignment;
-(id) init {
if (self = [super init]) {
_text = #"";
_textColor = [UIColor blackColor];
_font = #"Helvetica";
_fontSize = 12;
_strokeColor = [UIColor whiteColor];
_strokeWidth = 0.0;
_textAlignment = kCTLeftTextAlignment;
_lineBreakMode = kCTLineBreakByWordWrapping;
}
return self;
}
-(void) dealloc {
[_text release];
[_textColor release];
[_font release];
[_strokeColor release];
[super dealloc];
}
-(void) setText:(NSString *)text {
[_text release];
_text = [text retain];
[self setNeedsDisplay];
}
-(void) setTextColor:(UIColor *)textColor {
[_textColor release];
_textColor = [textColor retain];
[self setNeedsDisplay];
}
-(void) setFont:(NSString *)font {
[_font release];
_font = [font retain];
[self setNeedsDisplay];
}
-(void) setFontSize:(float)fontSize {
_fontSize = fontSize;
[self setNeedsDisplay];
}
-(void) setStrokeColor:(UIColor *)strokeColor {
[_strokeColor release];
_strokeColor = strokeColor;
[self setNeedsDisplay];
}
-(void) setStrokeWidth:(float)strokeWidth {
_strokeWidth = 0 ? (strokeWidth < 0) : (-1 * strokeWidth);
[self setNeedsDisplay];
}
-(void) setTextAlignment:(CTTextAlignment)textAlignment {
_textAlignment = textAlignment;
[self setNeedsDisplay];
}
-(void) setFrame:(CGRect)frame {
[super setFrame: frame];
[self setNeedsDisplay];
}
-(float) suggestedHeightForWidth:(float) width {
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)_font, _fontSize, NULL);
CTParagraphStyleSetting paragraphStyles[2] = {
{.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void *) &_lineBreakMode},
{.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void *) &_textAlignment}
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStyles, 2);
NSDictionary *attrDict = [[NSDictionary alloc] initWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)_textColor.CGColor, (NSString *)(kCTForegroundColorAttributeName), (id)_strokeColor.CGColor, (NSString *)(kCTStrokeColorAttributeName), (id)[NSNumber numberWithFloat: _strokeWidth], (NSString *)(kCTStrokeWidthAttributeName), (id)paragraphStyle, (NSString *)(kCTParagraphStyleAttributeName), nil];
CFRelease(fontRef);
CFRelease(paragraphStyle);
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:_text attributes: attrDict];
// Determine suggested frame height
CFRange textRange = CFRangeMake(0, [attrStr length]);
CGSize constraint = CGSizeMake(width, 9999);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, constraint, NULL);
textSize = CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
[attrDict release];
[attrStr release];
return textSize.height;
}
-(void) renderText:(CGContextRef)ctx {
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)_font, _fontSize, NULL);
CTParagraphStyleSetting paragraphStyles[2] = {
{.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void *) &_lineBreakMode},
{.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void *) &_textAlignment}
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStyles, 2);
NSDictionary *attrDict = [[NSDictionary alloc] initWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)_textColor.CGColor, (NSString *)(kCTForegroundColorAttributeName), (id)_strokeColor.CGColor, (NSString *)(kCTStrokeColorAttributeName), (id)[NSNumber numberWithFloat: _strokeWidth], (NSString *)(kCTStrokeWidthAttributeName), (id)paragraphStyle, (NSString *)(kCTParagraphStyleAttributeName), nil];
CFRelease(fontRef);
CFRelease(paragraphStyle);
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:_text attributes: attrDict];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CFRange textRange = CFRangeMake(0, [attrStr length]);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL);
CFArrayRef lines = CTFrameGetLines(frame);
NSInteger numberOfLines = CFArrayGetCount(lines);
CGPoint lineOrigins[numberOfLines];
CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
CGPoint lineOrigin = lineOrigins[lineIndex];
CGContextSetTextPosition(ctx, lineOrigin.x, lineOrigin.y);
CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
if (lineIndex == numberOfLines - 1) {
CFRange lastLineRange = CTLineGetStringRange(line);
if (!(lastLineRange.length == 0 && lastLineRange.location == 0) && lastLineRange.location + lastLineRange.length < textRange.location + textRange.length) {
NSUInteger truncationAttributePosition = lastLineRange.location;
CTLineTruncationType truncationType;
if (numberOfLines != 1) {
truncationType = kCTLineTruncationEnd;
truncationAttributePosition += (lastLineRange.length - 1);
}
NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:#"\u2026" attributes:attrDict];
CTLineRef truncationToken = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString);
NSMutableAttributedString *truncationString = [[attrStr attributedSubstringFromRange: NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy];
if (lastLineRange.length > 0) {
unichar lastCharacter = [[truncationString string] characterAtIndex: lastLineRange.length - 1];
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
[truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)];
}
}
[truncationString appendAttributedString: tokenString];
CTLineRef truncationLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationString);
CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, self.bounds.size.width, truncationType, truncationToken);
if (!truncatedLine) {
// If the line is not as wide as the truncationToken, truncatedLine is NULL
truncatedLine = CFRetain(truncationToken);
}
CTLineDraw(truncatedLine, ctx);
CFRelease(truncatedLine);
CFRelease(truncationLine);
CFRelease(truncationToken);
} else {
CTLineDraw(line, ctx);
}
} else {
CTLineDraw(line, ctx);
}
}
[attrStr release];
[attrDict release];
CFRelease(path);
CFRelease(frame);
CFRelease(framesetter);
}
-(void) drawInContext:(CGContextRef)ctx {
[super drawInContext: ctx];
[self renderText: ctx];
}
#end
I think to support both you can create a category for text layers, in category you can code it conditionally for both versions.
Same as we do for navigation bar when we change image.
You can center your frame as you did with different frames for different ios versions
It seems to me that iOS 6 has taken into account the Line Height (or other font related features that affects the actual vertical drawing position of the glyph) of the font when drawing the text contents of CATextLayer. The result is that in iOS 6.0, the text with certain font in CATextLayer is not displayed at the top edge of the frame of the CATextLayer. I found that some font has such vertical padding while others don't. While in iOS 5.0/5.1, the glyph of the text is actually displayed at the top edge of the frame of the CATextLayer.
So one possible solution I'm thinking may be to change the textLayer object in your code from CATextLayer to just CALayer (or subclass CALayer) and use Core Text to custom draw the contents such that you get to control of everything that will be consistent across iOS 5.0/5.1 and 6.0.

ios bar that changes colors while loading

I am currently working on a project of my own and I would like to implement a custom loading control.
It's pretty much what you can find in the app Amen. The colored bar appears only when the app is loading content from the server.
Any tips will be much appreciated. Thanks.
Here are some images to make it more clear
The "hard" part will be writing a UIView subclass that will handle drawing the colors. You'll want to override the drawRect: method and figure out how to know the progress (or will it just "auto-increment"?) and draw/fill in based on that. Then, you can simply add a UIView in Interface Builder, change the Class type of the view, size it appropriately and off you go!
The "easy" part is that when you want the view to not be visible, you can do one of a number of things:
Move the view off-screen by changing its frame property. (This can be "instantaneous" or animated.)
Set the view invisible by changing its hidden property. (You can animate this, too!)
Get rid of the view entirely by using [barView removeFromSuperview].
Update/Edit
For the actual drawing, try this (done quickly and not tested, so YMMV):
// ColorProgressBar.h
#import <UIKit/UIKit.h>
#interface ColorProgressBar : UIView {
float colorWidth;
float progressPercent;
NSMutableArray *colors;
}
#property (assign, nonatomic) float colorWidth;
#property (assign, nonatomic) float progressPercent;
#property (strong, nonatomic) NSMutableArray *colors;
#end
// ColorProgressBar.m
#import "ColorProgressBar.h"
#implementation ColorProgressBar
#synthesize colors;
#synthesize colorWidth;
#synthesize progressPercent;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Make the color array to use (this is spelled out for clarity)
[self setColors:[NSMutableArray array]];
[[self colors] addObject:[UIColor redColor]];
[[self colors] addObject:[UIColor orangeColor]];
[[self colors] addObject:[UIColor yellowColor]];
[[self colors] addObject:[UIColor greenColor]];
[[self colors] addObject:[UIColor blueColor]];
[[self colors] addObject:[UIColor purpleColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGFloat left = 0;
CGRect drawBox = CGRectZero;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextClearRect(ctx, rect);
int colorIndex = -1;
// Draw each color block from left to right, switching to the next color each time
do {
colorIndex = colorIndex + 1;
if (colorIndex >= [[self colors] count]) colorIndex = 0;
[(UIColor *)[[self colors] objectAtIndex:colorIndex] setFill];
drawBox = CGRectMake(left, 0, [self colorWidth], rect.size.height);
CGContextFillRect(ctx, drawBox);
} while (left < rect.size.width);
// Figure out where the "faded/empty" part should start
left = [self progressPercent] * rect.size.width;
drawBox = CGRectMake(left, 0, rect.size.width - left, rect.size.height);
[[UIColor colorWithWhite:1.0 alpha:0.5] setFill];
CGContextFillRect(ctx, drawBox);
}
#end
With this code, you could use this UIView subclass and each time you wanted to update your progress, you would simply set your progressPercent (it's a float with a designed range from 0.00 to 1.00) and call [myView setNeedsDisplay]. That should be it! ;-)

Resources