Underline Text on the iPhone

Underline Text on the iPhone

One of the least talked about feature deficiencies of Apple iPhone SDK Font Handling is the inability to underline text.

You know – like you see in every web link on every web page on the internet.

Yeah.  That.

For me, where I need this the most is when I’m trying to create a link on one of my text sections or display elements, that will invoke a web URI and go off to do some work.

There isn’t a way to style underline fonts included with the iPhone, and Apple doesn’t make it easy at all to include custom fonts without a great deal of pain and gnashing of teeth.

What is one to do?

Well, I don’t know what the rest of you guys do, but this is what I did.

I created a button class (UnderlineButton) that subclasses UIButton and implemented my own drawRect function to draw the underline myself.

This isn’t rocket science, but works quite nicely.  The button should probably have a number of init functions to do things like set underline stroke width and color, but I needed a quick and dirty solution and had about 30 minutes to write, test, and deliver.

The code below is what I wound up with (UnderlineButton.m follows; UnderlineButton.h is simply a stub class that contains only @interface UnderlineButton : UIButton {}).


//
//  UnderlineButton.m
//
//  Created by David Hinson on 11/24/09.
//  Copyright 2009 Sumner Systems Management, Inc.. All rights reserved.
//

#import "UnderlineButton.h"
@implementation UnderlineButton

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    // Initialization code
    }
  return self;
}
- (void)drawRect:(CGRect)rect {
  [super drawRect:rect];
  CGContextRef context = UIGraphicsGetCurrentContext();

  CGContextSetRGBStrokeColor(context, 62.0/255.0, 62.0/255.0, 62.0/255.0, 1.0);

  // Draw them with a 1.0 stroke width.
  CGContextSetLineWidth(context, 1.0);

  // Draw a single line from left to right
  CGContextMoveToPoint(context, 0, rect.size.height);
  CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
  CGContextStrokePath(context);
}

- (void)dealloc {
  [super dealloc];
}

@end

To use the button in an actual application, you would do something like the following:

  CGSize constraintSize, offset1;
  constraintSize.width  = 300.0f;
  constraintSize.height = MAXFLOAT;

  NSString * btnText = @"My Button Text";

  UnderlineButton * myButton = [[UnderlineButton buttonWithType:UIButtonTypeCustom] retain];
  offset1                    = [btnText sizeWithFont:[UIFont systemFontOfSize:16]
                                constrainedToSize:constraintSize
                                lineBreakMode:UILineBreakModeTailTruncation];
  myButton.frame = CGRectMake(20, 164, offset1.width, offset1.height);
  [myButton setTitle:btnText forState:UIControlStateNormal];

  [myButton setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal];
  [myButton setFont:[UIFont systemFontOfSize:16]];
  [myButton addTarget:self action:@selector(doButtonTouch:)
            forControlEvents:UIControlEventTouchUpInside];

  [cell myButton];
  [myButton release];

The doButtonTouch method will perform whatever it is you want to do; in my case this invoking another method to slide in a UIWebView to show drill down content.

Summary: Underline Text. Nothing there to make it natively easy, but workable solutions can be cobbled together.

But I will be the very first to say “it shouldn’t be this hard.

14 thoughts on “Underline Text on the iPhone

  1. Here is an improved version of the drawRect routine that uses the same color as the font for the underline and draws the line just under the text instead of at the very bottom of the label.

    – (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetStrokeColorWithColor(context, self.currentTitleColor.CGColor);

    // Draw them with a 1.0 stroke width.
    CGContextSetLineWidth(context, 1.0);

    CGFloat baseline = rect.size.height + self.titleLabel.font.descender + 2;

    // Draw a single line from left to right
    CGContextMoveToPoint(context, 0, baseline);
    CGContextAddLineToPoint(context, rect.size.width, baseline);
    CGContextStrokePath(context);
    }

    Like

    1. small change to draw the line only to the width of the text

      – (void)drawRect:(CGRect)rect {
      [super drawRect:rect];

      CGContextRef context = UIGraphicsGetCurrentContext();

      CGContextSetStrokeColorWithColor(context, self.currentTitleColor.CGColor);

      // Draw them with a 1.0 stroke width.
      CGContextSetLineWidth(context, 2.0);

      // Work out line width
      NSString *text = self.titleLabel.text;
      CGSize sz = [text sizeWithFont:self.titleLabel.font forWidth:rect.size.width lineBreakMode:UILineBreakModeWordWrap];
      CGFloat width = rect.size.width;
      CGFloat offset = (rect.size.width – sz.width) / 2;
      if (offset > 0 && offset < rect.size.width)
      width -= offset;
      else
      offset = 0.0;

      // Work out line spacing to put it just below text
      CGFloat baseline = rect.size.height + self.titleLabel.font.descender + 2;

      // Draw a single line from left to right
      CGContextMoveToPoint(context, offset, baseline);
      CGContextAddLineToPoint(context, width, baseline);
      CGContextStrokePath(context);
      }

      Like

      1. this doesn’t seem to work for iPhone4 retina display. It underlines only half the text. What can I tweak to make it work for retina? Thanks.

        Like

      2. I guess test the device to see if it is an iPhone 4 and double the width. I’ll leave it as an exercise for the reader.

        Like

  2. Hey David,

    I am an iPhone developer working my way into Android now, I was curious if there is a similar function to [NSString sizeWithFont:] in the Android sdk. Any ideas?

    Like

  3. To completely ressemble Web-link-like underlined button you need to update the color of the line if the color of title changes (what they usually do in HTML).

    A bit of code to support it:

    – (id)initWithFrame: (CGRect)frame {
    if (self = [super initWithFrame: frame]) {
    [self addTarget: self action: @selector(touchDown:) forControlEvents: UIControlEventTouchDown];
    [self addTarget: self action: @selector(touchDown:) forControlEvents: UIControlEventTouchDragEnter];

    [self addTarget: self action: @selector(touchUp:) forControlEvents: UIControlEventTouchUpInside];
    [self addTarget: self action: @selector(touchUp:) forControlEvents: UIControlEventTouchUpOutside];
    [self addTarget: self action: @selector(touchUp:) forControlEvents: UIControlEventTouchCancel];
    [self addTarget: self action: @selector(touchUp:) forControlEvents: UIControlEventTouchDragExit];
    }

    return self;
    }

    …. draw rect code ….

    – (void)touchDown: (id)sender
    {
    debug(@”touch DOWN”);
    [self setNeedsDisplay];
    }

    – (void)touchUp: (id)sender
    {
    debug(@”touch UP”);
    [self setNeedsDisplay];
    }

    Like

Leave a comment