Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore Ascender and Descender when centering UILabel vertically?

I’m using AutoLayout to position some labels in the vertical centre of a cell. The text is in all-caps, but the UILabel in question, even when sizeToFit is applied, leaves space below the text, which looks a lot like it would be for the tails on letters such as a lower case y, p, and q. Since I’m centring vertically, this is causing an offset and meaning the text appears a few pixels higher than it should do.

Another question may be: can I have a font intelligently adjust its vertical centre dependant on whether it contains any characters which use the ascender or descender?

For instance, the string “abbaba” doesn’t need the descender, whereas the string “oyyoyo” doesn’t need the ascender. Strings in all-caps also never need the descender. If I vertically center “oyyoyoyo” it’ll appear too low.

enter image description here

like image 823
Luke Avatar asked Jan 14 '14 09:01

Luke


3 Answers

Thanks, Abhinit, for your answer.

I was also looking for this so I would like to post here the exact constraints you need to apply to align texts to your liking.

This image from Wikipedia shows the different size sections of a font.

enter image description here

So, there are many ways to align a label depending on whether you want to align to the ascender height, the cap height, the x-height, baseline or descender height.

Let's say you have a label containing text in caps like "HELLO" and you want to align with viewAbove to cap height and align with viewBelow to baseline.

You would do:

let font = label.font
let ascenderDelta = font.ascender - font.capHeight

LayoutHelper()
    .addViews([
        "label":label, "viewAbove":viewAbove, "viewBelow":viewBelow
    ])
    .withMetrics(["ascenderDelta":ascenderDelta])
    .addConstraints([

        // -- Here the constraints to align to cap height --
        "X:label.top == viewAbove.bottom - ascenderDelta",
        "X:label.baseline == viewBelow.top",

        ...other constraints...
    ])

Note: in the example I'm using my utility class LayoutHelper, but I hope the idea is clear.

About an "auto-aligning" label:

I will think about making an "intelligent" label that adjusts to the appropriate line depending on whether it contains descenders, ascenders, caps, etc.

You could do it using negative insets in drawTextInRect(like here but, for example, using insets = {-ascenderDelta, 0, font.descender, 0}). But that would crop any ascenders/descenders in case you had. I would prefer to align to caps without cropping any possible ascender.

like image 67
Ferran Maylinch Avatar answered Nov 07 '22 12:11

Ferran Maylinch


You can use the 'capHeight' and 'xHeight' properties on UIFont to get the correct height and use that to size the UILabel.

Of course this assumes that you know for sure if a string would be lowercase or uppercase only. If not, then you can override setText on a UILabel and check every time the function gets called.

I would also think of looking deeper into CoreText and implementing something like this http://www.zsiegel.com/2012/10/23/Core-Text-Calculating-line-heights/

like image 22
Abhinit Avatar answered Nov 07 '22 13:11

Abhinit


I had the same problem and solved it by subclassing UILabel and changing its draw method:

- (void)drawRect:(CGRect)rect
{
   CGRect capCenteredRect = CGRectMake(rect.origin.x, (self.font.leading-self.font.capHeight)*0.5f, rect.size.width, rect.size.height);
   [super drawTextInRect:capCenteredRect];
}

In my case I needed to center to caps because the vertical centering of the UILabel is always some pixels off. If you need to center vertically to lower case letters with descender you can change the rect to:

CGRectMake(rect.origin.x, (self.font.leading-self.font.xHeight)*0.5f+self.font.descender, rect.size.width, rect.size.height)
like image 3
Martin Polak Avatar answered Nov 07 '22 14:11

Martin Polak