Underlining text in UIButton

TextFormattingUibutton

Text Problem Overview


Can anyone suggest how to underline the title of a UIButton ? I have a UIButton of Custom type, and I want the Title to be underlined, but the Interface Builder does not provide any option to do so.

In Interface Builder when you select the Font Option for a Button, it provides option to select None, Single, Double, Color but none of these provide any changes to the Title on the Button.

Any help appreciated.

Text Solutions


Solution 1 - Text

To use interface builder to underline, one has to:

  • Change it to attributed
  • Highlight the text in the Attributes inspector
  • Right click, choose Font and then Underline

Underline using IB

Video someone else made https://www.youtube.com/watch?v=5-ZnV3jQd9I

Solution 2 - Text

From iOS6 it is now possible to use an NSAttributedString to perform underlining (and anything else attributed strings support) in a much more flexible way:

NSMutableAttributedString *commentString = [[NSMutableAttributedString alloc] initWithString:@"The Quick Brown Fox"];

[commentString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [commentString length])];

[button setAttributedTitle:commentString forState:UIControlStateNormal];

Note: added this as another answer - as its a totally different solution to my previous one.

Edit: oddly (in iOS8 at least) you have to underline the first character otherwise it doesn't work!

so as a workaround, set the first char underlined with clear colour!

    // underline Terms and condidtions
	NSMutableAttributedString* tncString = [[NSMutableAttributedString alloc] initWithString:@"View Terms and Conditions"];
	
	// workaround for bug in UIButton - first char needs to be underlined for some reason!
	[tncString addAttribute:NSUnderlineStyleAttributeName
					  value:@(NSUnderlineStyleSingle)
					  range:(NSRange){0,1}];
	[tncString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:NSMakeRange(0, 1)];

	
	[tncString addAttribute:NSUnderlineStyleAttributeName
					  value:@(NSUnderlineStyleSingle)
					  range:(NSRange){5,[tncString length] - 5}];
	
	[tncBtn setAttributedTitle:tncString forState:UIControlStateNormal];

Solution 3 - Text

UIUnderlinedButton.h

@interface UIUnderlinedButton : UIButton {

}


+ (UIUnderlinedButton*) underlinedButton;
@end

UIUnderlinedButton.m

@implementation UIUnderlinedButton

+ (UIUnderlinedButton*) underlinedButton {
	UIUnderlinedButton* button = [[UIUnderlinedButton alloc] init];
	return [button autorelease];
}

- (void) drawRect:(CGRect)rect {
	CGRect textRect = self.titleLabel.frame;
	
	// need to put the line at top of descenders (negative value)
	CGFloat descender = self.titleLabel.font.descender;
	
	CGContextRef contextRef = UIGraphicsGetCurrentContext();
	
	// set to same colour as text
	CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
	
	CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
	
	CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
	
	CGContextClosePath(contextRef);
	
	CGContextDrawPath(contextRef, kCGPathStroke);
}


@end

Solution 4 - Text

You can do it in the interface builder itself.

  1. Select the attribute inspector
  2. Change the title type from plain to attributed

enter image description here

  1. Set appropriate font size and text alignment

enter image description here

  1. Then select the title text and set the font as underlined

enter image description here

Solution 5 - Text

It is very simple with attributed string

Creates a dictionary with set attributes and apply to the attributed string. Then you can set the attributed string as attibutedtitle in uibutton or attributedtext in uilabel.

NSDictionary *attrDict = @{NSFontAttributeName : [UIFont
 systemFontOfSize:14.0],NSForegroundColorAttributeName : [UIColor
 whiteColor]};
 NSMutableAttributedString *title =[[NSMutableAttributedString alloc] initWithString:@"mybutton" attributes: attrDict]; 
[title addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0,[commentString length])]; [btnRegLater setAttributedTitle:title forState:UIControlStateNormal];

Solution 6 - Text

Here is my function, works in Swift 1.2.

func underlineButton(button : UIButton, text: String) {
    
    var titleString : NSMutableAttributedString = NSMutableAttributedString(string: text)
    titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, count(text.utf8)))
    button.setAttributedTitle(titleString, forState: .Normal)
}

UPDATE Swift 3.0 extension:

extension UIButton {
    func underlineButton(text: String) {
        let titleString = NSMutableAttributedString(string: text)
        titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
        self.setAttributedTitle(titleString, for: .normal)
    }
}

Solution 7 - Text

Nick's answer is a great, quick way to do this.

I added support in drawRect for shadows.

Nick's answer doesn't take into account if your button title has a shadow below the text:

enter image description here

But you can move the underline down by the height of the shadow like so:

CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGFloat shadowHeight = self.titleLabel.shadowOffset.height;
descender += shadowHeight;

Then you'll get something like this:

enter image description here

Solution 8 - Text

The Swift 5.0 version that works as of September 2019 in Xcode 10.3:

extension UIButton {
  func underlineText() {
    guard let title = title(for: .normal) else { return }

    let titleString = NSMutableAttributedString(string: title)
    titleString.addAttribute(
      .underlineStyle,
      value: NSUnderlineStyle.single.rawValue,
      range: NSRange(location: 0, length: title.count)
    )
    setAttributedTitle(titleString, for: .normal)
  }
}

To use it, set your button title first with button.setTitle("Button Title", for: .normal) and then call button.underlineText() to make that title underlined.

Solution 9 - Text

For Swift 3 the following extension can be used:

extension UIButton {
    func underlineButton(text: String) {
        let titleString = NSMutableAttributedString(string: text)
        titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
        self.setAttributedTitle(titleString, for: .normal)
    }
}

Solution 10 - Text

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;

// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;

CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIColor *colr;
// set to same colour as text
if (self.isHighlighted || self.isSelected) {
    colr=self.titleLabel.highlightedTextColor;
}
else{
    colr= self.titleLabel.textColor;
}
CGContextSetStrokeColorWithColor(contextRef, colr.CGColor);

CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y +        textRect.size.height + descender);

CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);

CGContextClosePath(contextRef);

CGContextDrawPath(contextRef, kCGPathStroke);
}
//Override this to change the underline color to highlighted color
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
// [self setNeedsDisplay];
}

Solution 11 - Text

Expanding on the answer by @Nick H247, I experienced an issue where firstly the underline was not redrawing when the button resized on rotation; this can be solved by setting your button to redraw like so:

myButton.contentMode = UIViewContentModeRedraw; 

This forces the button to redraw when the bounds change.

Secondly, the original code assumed you only had 1 line of text in the button (my button wraps to 2 lines on rotation) and the underline only appears on the last line of text. The drawRect code can be modified to first calculate the number of lines in the button, then put an underline on every line rather than just the bottom, like so:

 - (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;

// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;

CGContextRef contextRef = UIGraphicsGetCurrentContext();

// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);

CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
                            constrainedToSize:self.titleLabel.frame.size
                                lineBreakMode:UILineBreakModeWordWrap];

CGSize labelSizeNoWrap = [self.titleLabel.text sizeWithFont:self.titleLabel.font forWidth:self.titleLabel.frame.size.width lineBreakMode:UILineBreakModeMiddleTruncation ];

int numberOfLines = abs(labelSize.height/labelSizeNoWrap.height);

for(int i = 1; i<=numberOfLines;i++) {
 //        Original code
 //        CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender + PADDING);
 //        
 //        CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
    
    CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + (labelSizeNoWrap.height*i) + descender + PADDING);
    
    CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + (labelSizeNoWrap.height*i) + descender);
    
    CGContextClosePath(contextRef);
    
    CGContextDrawPath(contextRef, kCGPathStroke);
    
}


}

Hope this code helps someone else!

Solution 12 - Text

In swift

func underlineButton(button : UIButton) {

var titleString : NSMutableAttributedString = NSMutableAttributedString(string: button.titleLabel!.text!)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, button.titleLabel!.text!.utf16Count))
button.setAttributedTitle(titleString, forState: .Normal)}

Solution 13 - Text

You can use this code to add underline with spacing in button.

  • When I tried to draw an underline from interface builder. It look like below image.

1 - Interface builder reference

enter image description here

  • And after using below code I achieved the result as I wanted.

2 - using described code

enter image description here

public func setTextUnderline()
    {
        let dummyButton: UIButton = UIButton.init()
        dummyButton.setTitle(self.titleLabel?.text, for: .normal)
        dummyButton.titleLabel?.font = self.titleLabel?.font
        dummyButton.sizeToFit()
        
        let dummyHeight = dummyButton.frame.size.height + 3
        
        let bottomLine = CALayer()
        bottomLine.frame = CGRect.init(x: (self.frame.size.width - dummyButton.frame.size.width)/2, y: -(self.frame.size.height - dummyHeight), width: dummyButton.frame.size.width, height: 1.0)
        bottomLine.backgroundColor = self.titleLabel?.textColor.cgColor
        self.layer.addSublayer(bottomLine)
    }

Solution 14 - Text

How will one handle the case when we keep a button underlined pressed? In that case the button's textcolor changes according to highlighted color but line remains of original color. Let say if button text color in normal state is black then its underline will also have black color. The button's highlighted color is white. Keeping button pressed changes button text color from black to white but underline color remains black.

Solution 15 - Text

I believe it's some bug in font editor in XCode. If you using interface builder you have to change title from Plain to Attributed, open TextEdit create underlined text and copy-paste to textbox in XCode

Solution 16 - Text

Nick H247's answer but Swift approach:

import UIKit

class UnderlineUIButton: UIButton {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)
        
        let textRect = self.titleLabel!.frame
        
        var descender = self.titleLabel?.font.descender
        
        var contextRef: CGContextRef = UIGraphicsGetCurrentContext();
        
        CGContextSetStrokeColorWithColor(contextRef, self.titleLabel?.textColor.CGColor);
        
        CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender!);
        
        CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender!);
        
        CGContextClosePath(contextRef);
        
        CGContextDrawPath(contextRef, kCGPathStroke);
    }
}

Solution 17 - Text

func underline(text: String, state: UIControlState = .normal, color:UIColor? = nil) {
        var titleString = NSMutableAttributedString(string: text)
        
        if let color = color {
            titleString = NSMutableAttributedString(string: text,
                               attributes: [NSForegroundColorAttributeName: color])
        }
        
        let stringRange = NSMakeRange(0, text.characters.count)
        titleString.addAttribute(NSUnderlineStyleAttributeName,
                                 value: NSUnderlineStyle.styleSingle.rawValue,
                                 range: stringRange)
        
        self.setAttributedTitle(titleString, for: state)
    }

Solution 18 - Text

Swift 3 version for @NickH247's answer with custom underline color, linewidth and gap:

import Foundation

class UnderlinedButton: UIButton {
    
    private let underlineColor: UIColor
    private let thickness: CGFloat
    private let gap: CGFloat
    
    init(underlineColor: UIColor, thickness: CGFloat, gap: CGFloat, frame: CGRect? = nil) {
        self.underlineColor = underlineColor
        self.thickness = thickness
        self.gap = gap
        super.init(frame: frame ?? .zero)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard let textRect = titleLabel?.frame,
            let decender = titleLabel?.font.descender,
            let context = UIGraphicsGetCurrentContext() else { return }
        
        context.setStrokeColor(underlineColor.cgColor)
        context.move(to: CGPoint(x: textRect.origin.x, y: textRect.origin.y + textRect.height + decender + gap))
        context.setLineWidth(thickness)
        context.addLine(to: CGPoint(x: textRect.origin.x + textRect.width, y: textRect.origin.y + textRect.height + decender + gap))
        context.closePath()
        context.drawPath(using: .stroke)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRVNView Question on Stackoverflow
Solution 1 - TextfinneycanhelpView Answer on Stackoverflow
Solution 2 - TextNick HingstonView Answer on Stackoverflow
Solution 3 - TextNick HingstonView Answer on Stackoverflow
Solution 4 - TextLineesh K MohanView Answer on Stackoverflow
Solution 5 - TextRinkuView Answer on Stackoverflow
Solution 6 - TextAdam StudenicView Answer on Stackoverflow
Solution 7 - TextannieView Answer on Stackoverflow
Solution 8 - TextMax DesiatovView Answer on Stackoverflow
Solution 9 - TextDurga VundavalliView Answer on Stackoverflow
Solution 10 - TextRohitView Answer on Stackoverflow
Solution 11 - TextAndroidNoobView Answer on Stackoverflow
Solution 12 - TextArshadView Answer on Stackoverflow
Solution 13 - TextAyaz RafaiView Answer on Stackoverflow
Solution 14 - TextParvez QureshiView Answer on Stackoverflow
Solution 15 - TextdanghView Answer on Stackoverflow
Solution 16 - Textel.severoView Answer on Stackoverflow
Solution 17 - TextLuAndreView Answer on Stackoverflow
Solution 18 - TextbughanaView Answer on Stackoverflow