152

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.

Jubal
  • 8,357
  • 5
  • 29
  • 30
RVN
  • 4,157
  • 5
  • 32
  • 35
  • 1
    You can use UITextView with attributed string adding a link to it as in this question http://stackoverflow.com/questions/21629784/how-to-make-a-clickable-link-in-an-nsattributedstring-for-a – Khaled Annajar Oct 19 '16 at 07:13

18 Answers18

391

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

finneycanhelp
  • 9,018
  • 12
  • 53
  • 77
  • What about text that have to be localised, @finneycanhelp? I have custom localisation - inside the App and using the method provided the text is translated. – new2ios Apr 30 '15 at 12:52
  • 2
    Good question @new2ios Perhaps someone else knows – finneycanhelp Apr 30 '15 at 12:54
  • @new2ios you could see if one of the other answers works for your case or post a new question, refer to this one, and say how yours is different. I sympathize. – finneycanhelp Apr 30 '15 at 12:57
  • 1
    I will ask new question, @finneycanhelp. I hope that in Xcode 6.3 there will be more easier way. I mean that may be I can set your solution and then use `setTitle` with attributed text. For me creating custom button to draw underline is a bit exotic (may be I am still new to iOS even having one App completed). – new2ios Apr 30 '15 at 12:58
  • 2
    finneydonehelped! thanks for this! couldn't figure out why the Fonts popup dialog had no effect. The right-click is perfect. – IMFletcher Dec 01 '15 at 21:45
  • 2
    Good answer for Interface Builder users for this kind of simple things which are little bit of work when doing in Code. Thanks! (Y) – Randika Vishman Mar 22 '16 at 08:37
  • Yes I have done the same now I have a query that how to change underline colour only? – Anil Kukadeja May 19 '16 at 07:47
  • it only set static text – kemdo Apr 02 '18 at 17:58
  • 1
    Why do iOS developers prefers to write a very long code just for a very simple issue? – mr5 Jun 24 '19 at 06:53
  • 1
    What a joke. Right click? I feel so stupid now. Tried to find this for good 20 mins. Thanks. – Rishab Aug 30 '19 at 11:07
130

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];
Nick Hingston
  • 8,724
  • 3
  • 50
  • 59
  • 9
    Just be aware that when you do it this way, you must also add on attribute for the color, as the attributed title text will not use the color you set using setTitleColor:forState: – daveMac Aug 16 '13 at 17:15
  • 2
    Awesome, and thanks @daveMac for the heads up on the color. For those who don't know the attribute it's: NSForegroundColorAttributeName – Ryan Crews Dec 01 '14 at 01:11
  • in this methods the button underline is close to text any method to change the y position of underline ? – Ilesh P Jan 19 '16 at 07:15
79

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
ingh.am
  • 25,981
  • 43
  • 130
  • 177
Nick Hingston
  • 8,724
  • 3
  • 50
  • 59
  • 1
    maybe not as timely as you needed! – Nick Hingston Oct 13 '10 at 15:48
  • 4
    Thanks, I ended up calling it as follows: UIButton *btn = [UIUnderlinedButton buttonWithType:UIButtonTypeCustom]; – hb922 Mar 21 '13 at 21:11
  • 2
    The code works well, but I noticed the underline did not shrink/grow when the view changes size on rotation, caused by `drawRect` not being called on rotation. This can be solved by setting your button to redraw like so: `myButton.contentMode = UIViewContentModeRedraw;` which forces the button to redraw when the bounds change. – AndroidNoob Aug 07 '13 at 15:03
  • 4
    You can also override the `setTitle` method like so : ```objective-c - (void)setTitle:(NSString *)title forState:(UIControlState)state { [super setTitle:title forState:state]; [self setNeedsDisplay]; }``` – Alexis C. Oct 14 '13 at 15:41
51

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

Lineesh K Mohan
  • 1,702
  • 14
  • 18
29

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];
Rinku
  • 910
  • 13
  • 28
22

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)
    }
}
Adam Studenic
  • 2,115
  • 2
  • 25
  • 22
18

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.

Max Desiatov
  • 5,087
  • 3
  • 48
  • 56
  • 1
    I can confirm this works on versions as old as iOS 10.3.1, Xcode 10.3 doesn't support simulators older than that on Mojave, as far as I'm aware. – Max Desiatov Nov 28 '19 at 13:49
13

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

annie
  • 1,347
  • 1
  • 13
  • 25
5

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)
    }
}
Durga Vundavalli
  • 1,790
  • 22
  • 26
4
// 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];
}
Rohit
  • 49
  • 1
3

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!

AndroidNoob
  • 2,771
  • 2
  • 40
  • 53
3

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)}
Arshad
  • 906
  • 7
  • 12
3

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)
    }
Ravi B
  • 1,574
  • 2
  • 14
  • 31
Ayaz Rafai
  • 279
  • 2
  • 10
  • Thank you for this code snippet, which might provide some limited, immediate help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Sep 22 '17 at 10:47
2

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

dangh
  • 176
  • 2
  • 2
2

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);
    }
}
el.severo
  • 2,202
  • 6
  • 31
  • 62
2
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)
    }
LuAndre
  • 1,114
  • 12
  • 23
2

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.

  • 2
    You could test if the button is highlighted and or selected, and set the colour accordingly. not sure if redraw will be requested automatically, if not you would need to override setSelected/setHighlighted and call super and [self setNeedsDisplay] – Nick Hingston Feb 15 '12 at 17:31
1

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")
    }
}
bughana
  • 324
  • 5
  • 12