There has been a lot of confusion about what I am actually trying to achieve. So please let me reword my question. It is as simple as:
How does Apple center text inside
UILabel
's-drawTextInRect:
?
Here's some context on why I am looking for Apple's approach to center text:
- I am not happy with how they center text inside this method
- I have my own algorithm that does the job the way I want
However, there are some places I can't inject this algorithm into, not even with method swizzling. Getting to know the exact algorithm they use (or an equivalent of that) would solve my personal problem.
Here's a demo project you can experiment with. In case you're interested what my problem with Apple's implementation is, look at the left side (base UILabel): The text jumps up and down as you increase the font size using the slider, instead of gradually moving downwards.
Again, all I am looking for is an implementation of -drawTextInRect:
that does the very same as base UILabel
-- but without calling super
.
- (void)drawTextInRect:(CGRect)rect
{
CGRect const centeredRect = ...;
[self.attributedText drawInRect:centeredRect];
}
After two years and a week at WWDC I finally figured out what the hell is going on.
As for why
UILabel
behaves the way it does: It looks like Apple attempts to center whatever lies between ascender and descender. However, the actual text drawing engine always snaps the baseline to pixel boundaries. If one doesn't pay attention to this when calculating the rect in which to draw the text, the baseline may jump up and down seemingly arbitrarily.The main problem is finding where Apple places the baseline. AutoLayout offers a firstBaselineAnchor, but it's value can unfortunately not be read from code. The following snippet appears to correctly predict the baseline at all screen
scale
s andcontentScaleFactor
s as long as the label's height is rounded to pixels.When the label's height is not rounded to pixels, the predicted baseline is a pixel off more often than not, e.g. for a 13.0pt font in 40.1pt container on a 2x device or a 16.0pt font in 40.1pt container on a 3x device.
Note that we have to work at pixel level here. Working with points (i.e. dividing by screen scale immediately rather than at the end) results in off-by-one errors on @3x screens due to floating point inaccuracies.
For example, centering a 47px (15.667pt) content in a 121px container (40.333pt) gives us a 12.333pt offset which gets ceiled to 12.667pt due to floating point errors.
For sake of answering the question I initially asked, we can then implement
-drawTextInRect:
using the (hopefully correctly) predicted baseline to yield the same results asUILabel
does.See the project on GitHub for a working implementation.