Raon
Raon

Reputation: 1286

How to Vertically Align Text Using UILabel and NSAttributedString

I am using UILabel and NSAttributedString to set linespacing for label texts in IOS7. But when i use this the text doesnt seems aligned centrally on the Label. Here is my code to set text (attributed) to the label.

-(void)setText:(NSString *)text
{
    [super setText:text];

    if(text)
        [self setLineSpace];
}
-(void)setLineSpace
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7)
    {
        NSMutableAttributedString *string=[[NSMutableAttributedString alloc]initWithString:self.text];

        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
        paragraphStyle.alignment=NSTextAlignmentJustified;
        [paragraphStyle setLineSpacing:4] ;
//        paragraphStyle.minimumLineHeight =0;
//        paragraphStyle.maximumLineHeight=7;
     //  CTTextAlignment alignment = kCTCenterTextAlignment;
        [string addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, string.length)];
        self.text=nil;
        self.attributedText=string;

        [self setBackgroundColor:[UIColor redColor]];

    }

}

Here are some Screenshots ,BTW am subclassing UILabel to and overriding the setter to implement linespacing.

enter image description here

Upvotes: 4

Views: 17922

Answers (6)

Shaheen Ghiassy
Shaheen Ghiassy

Reputation: 7517

Converted the solution put up by Ivan M in https://discussions.apple.com/thread/1759957?tstart=0 to Swift 5

class VerticallyAlignedUILabel: UILabel {
    enum VerticalAlignment {
        case top
        case bottom
        case middle
    }

    public var verticalAlignment:VerticalAlignment {
        didSet {
            self.setNeedsDisplay()
        }
    }

    init(frame: CGRect, verticalAlignment:VerticalAlignment = .middle) {
        self.verticalAlignment = verticalAlignment
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        var textRect:CGRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)

        switch self.verticalAlignment {
        case .top:
            textRect.origin.y = bounds.origin.y
        case .bottom:
            textRect.origin.y = bounds.origin.y + bounds.size.height - textRect.size.height
        case .middle:
            textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) / 2.0
        }

        return textRect
    }

    override func drawText(in rect: CGRect) {
        let actualRect = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
        super.drawText(in: actualRect)
    }

}

Upvotes: 1

Casey
Casey

Reputation: 6691

Can be done via the attributed string:

ObjC:

// shift the text down 10 pixels
[string addAttribute:NSBaselineOffsetAttributeName value:@(-10) range:NSMakeRange(0, string.length)];

Swift:

// shift the text up 10 pixels
string.addAttributes([NSAttributedStringKey.baselineOffset:10], range: range)

Upvotes: 2

Raon
Raon

Reputation: 1286

Actually I solved it ....:) :) .The problem was with the custom font i was using.

Took some R&D but now it seems working

Just created a subclass for label and override some default methods.

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{

    CGRect textRect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];

    if(([self.font.fontName isEqualToString:@"Gotham"]||[self.font.fontName isEqualToString:@"Gotham-Bold"]||[self.font.fontName isEqualToString:@"Gotham-Medium"])&&((int)[[[UIDevice currentDevice] systemVersion] floatValue])==6&&self.verticalAlignment==AlignMiddle)
    {
        textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) / 2.0;
        return [self addToRect:textRect x:0 y:2];
    }
    return [self addToRect:textRect x:0 y:0.5];
}

-(void)drawTextInRect:(CGRect)requestedRect
{
    if(((int)[[[UIDevice currentDevice] systemVersion] floatValue])>=7)
       [super drawTextInRect:[self addToRect:requestedRect x:0 y:0.5]];
    else
    {
        CGRect actualRect = [self textRectForBounds:requestedRect limitedToNumberOfLines:self.numberOfLines];
        [super drawTextInRect:actualRect];
    }
}
-(CGRect)addToRect:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
    rect=CGRectMake(rect.origin.x+dx, rect.origin.y+dy, rect.size.width, rect.size.height);
    return rect;
}
-(CGRect)makeRect:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
    rect=CGRectMake(dx,dy, rect.size.width+dx, rect.size.height+dy);
    return rect;
}
-(CGRect)makeRects:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
    rect=CGRectMake(rect.origin.x,rect.origin.y, rect.size.width+dx, rect.size.height+dy);
    return rect;
}
-(void)truncateToFit
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] < 7)
    {
        [self sizeToFit];
        CGRect frame=self.frame;
    if(frame.size.height<self.frame.size.height)
        self.frame=[self makeRects:frame x:0 y:0.5];
    }
    else
    {
        self.frame=[self makeRects:self.frame x:0 y:0.5];
        [self sizeToFit];
    }
}

-(void)truncatesToFit
{
    self.lineBreakMode=NSLineBreakByTruncatingTail;

        self.numberOfLines=2;
        [self sizeToFit];
        self.frame=[self makeRects:self.frame x:0 y:0.5];
}




-(void)truncatesToFit:(NSInteger )linesCount

{

    self.numberOfLines=linesCount;

    [self sizeToFit];

    self.frame=[self makeRects:self.frame x:0 y:0.5];

}

Upvotes: 3

bilobatum
bilobatum

Reputation: 8918

When there's only one line of text displayed in your custom label, don't set the line spacing.

Upvotes: 0

Ravi Sisodia
Ravi Sisodia

Reputation: 776

Probably you can't do it with UILabel in the simple way.

If possible, you can use UITextField to vertically align content.

Else using NSAttributedString and the text of label, you can adjust Line-Height (Not sure).

Upvotes: 1

nerowolfe
nerowolfe

Reputation: 4817

You may use some other workaround - a custom UILabel

NWLabel.m

#import "NWLabel.h"

@implementation NWLabel

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return nil;

_verticalAlignment = VerticalAlignmentTop;

return self;
}


-(VerticalAlignment) verticalAlignment
{
return _verticalAlignment;
}

-(void) setVerticalAlignment:(VerticalAlignment)value
{
_verticalAlignment = value;
[self setNeedsDisplay];
}

// align text block according to vertical alignment settings
-(CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
CGRect rect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
CGRect result;
switch (_verticalAlignment)
{
    case VerticalAlignmentTop:
        result = CGRectMake(bounds.origin.x, bounds.origin.y, rect.size.width, rect.size.height);
        break;
    case VerticalAlignmentMiddle:
        result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height);
        break;
    case VerticalAlignmentBottom:
        result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height), rect.size.width, rect.size.height);
        break;
    default:
        result = bounds;
        break;
}
return result;
}

-(void)drawTextInRect:(CGRect)rect
{
CGRect r = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines];
[super drawTextInRect:r];
}

NWLabel.h

#import <UIKit/UIKit.h>

typedef enum
{
VerticalAlignmentTop = 0, // default
VerticalAlignmentMiddle,
VerticalAlignmentBottom,
} VerticalAlignment;

@interface NWLabel : UILabel
{
   @private  VerticalAlignment _verticalAlignment;
}

@property (nonatomic, readwrite, assign) VerticalAlignment verticalAlignment;


@end

Upvotes: 0

Related Questions