Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UILabel clipping italic (oblique) text at left and right edges of content ( iOS 6+)

Problem: UILabel may clip italic (oblique) characters and even scripts at the left and right edges. The following screenshot displays the issue. At the left edge, the descender of the 'j' is clipped; at the right edge, the ascender of the 'l' is clipped. I realize this is subtle, and not everyone is going to care (however, the issue gets worse with larger font sizes).

enter image description here

Here's a less subtle example using Zapfino, size 22. Note the 'j' in jupiter looks almost like an 'i':

enter image description here

In the examples above, the background color of the label is orange, the text is left aligned, and the label maintains its intrinsic content size.

This is the default behavior of a UILabel and its been that way for multiple versions of iOS (so I'm not expecting a fix from Apple).

What I have tried: Setting the label's clipsToBounds property to NO does not resolve the issue. I'm also aware that I could set a fixed width constraint on the label to give the text more room at the trailing edge. However, a fixed width constraint would not give the 'j', in the example above, more room.

I'm going to answer my own question using a solution that leverages Auto Layout and the label's alignmentRectInsets.

like image 245
bilobatum Avatar asked Jan 08 '14 00:01

bilobatum


2 Answers

The top label shows the default behavior of a UILabel when the text is left aligned that the label maintains its intrinsic content size. The bottom label is a simple (almost trivial) subclass of UILabel. The bottom label does not clip the 'j' or the 'l'; instead, it gives the text some room to breathe at the left and right edges without center aligning the text (yuck).

enter image description here

Although the labels themselves don't appear aligned on screen, their text does appear aligned; and what's more, in IB, the labels actually have their left edges aligned because I override alignmentRectInsets in a UILabel subclass.

enter image description here

Here's the code that configures the two labels:

#import "ViewController.h"
#import "NonClippingLabel.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *topLabel;
@property (weak, nonatomic) IBOutlet NonClippingLabel *bottomLabel;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *string = @"jupiter ariel";

    UIFont *font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:28];

    NSDictionary *attributes = @{NSFontAttributeName: font};

    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attributes];

    self.topLabel.attributedText = attrString;
    self.bottomLabel.attributedText = attrString;     
}

Here's the implementation of the NonClippingLabel subclass:

#import <UIKit/UIKit.h>

@interface NonClippingLabel : UILabel

@end

@implementation NonClippingLabel

#define GUTTER 4.0f // make this large enough to accommodate the largest font in your app

- (void)drawRect:(CGRect)rect
{
    // fixes word wrapping issue
    CGRect newRect = rect;
    newRect.origin.x = rect.origin.x + GUTTER;
    newRect.size.width = rect.size.width - 2 * GUTTER;
    [self.attributedText drawInRect:newRect];
}

- (UIEdgeInsets)alignmentRectInsets
{
    return UIEdgeInsetsMake(0, GUTTER, 0, GUTTER);
}

- (CGSize)intrinsicContentSize
{
    CGSize size = [super intrinsicContentSize];
    size.width += 2 * GUTTER;
    return size;
}

@end

No editing a font file, no using Core Text; just a relatively simple UILabel subclass for those using iOS 6+ and Auto Layout.

Update:

Augie caught the fact that my original solution prevented word wrapping for multi-lined text. I fixed that issue by using drawInRect: instead of drawAtPoint: to draw the text in the label's drawRect: method.

Here's a screenshot:

enter image description here

The top label is a plain-vanilla UILabel. The bottom label is a NonClippingLabel with an extreme gutter setting to accommodate Zapfino at size 22.0. Both labels are left and right aligned using Auto Layout.

enter image description here

like image 98
bilobatum Avatar answered Oct 04 '22 09:10

bilobatum


Swift version of NonClippingLabel with fixed sizeThatFits method from bilobatum answer.

class NonClippingLabel: UILabel {

    let gutter: CGFloat = 4

    override func draw(_ rect: CGRect) {
        super.drawText(in: rect.insetBy(dx: gutter, dy: 0))
    }

    override var alignmentRectInsets: UIEdgeInsets {
        return .init(top: 0, left: gutter, bottom: 0, right: gutter)
    }

    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        size.width += gutter * 2

        return size
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let fixedSize = CGSize(width: size.width - 2 * gutter, height: size.height)
        let sizeWithoutGutter = super.sizeThatFits(fixedSize)

        return CGSize(width: sizeWithoutGutter.width + 2 * gutter,
                      height: sizeWithoutGutter.height)
    }

}
like image 32
Vadim Akhmerov Avatar answered Oct 04 '22 08:10

Vadim Akhmerov