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 fromCTLineGetTypographicBounds
when passed aCTLine
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 fromCTLineGetTypographicBounds
was the one I needed. In the process, I discovered that a) the ascent fromUIFont ascender
,CTFontGetAscent
andCTLineGetTypographicBounds
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 offsettingCATextLayer
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) inCATextLayer
, 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 byUIFont 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 fromCTLineGetTypographicBounds
; transparent red is drawn byCATextLayer
with bounds equal to the grey box; transparent blue is drawn with theUIKit
NSString
additiondrawAtPoint:withFont:
locating at the origin of the grey box and with theUIFont
.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), butCTLineDraw
with ascent fromCTLineGetTypographicBounds
(black) matchesCATextLayer
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 fromCTLineGetTypographicBounds
(black) or fromUIFont
ascender used inNSString 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:
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.
t0rst's answer helps me. I think capHeight and xHeight are key.
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
CustomTextLayer.m