120

All I want is a one pixel black border around my white UILabel text.

I got as far as subclassing UILabel with the code below, which I clumsily cobbled together from a few tangentially related online examples. And it works but it's very, very slow (except on the simulator) and I couldn't get it to center the text vertically either (so I hard-coded the y value on the last line temporarily). Ahhhh!

void ShowStringCentered(CGContextRef gc, float x, float y, const char *str) {
    CGContextSetTextDrawingMode(gc, kCGTextInvisible);
    CGContextShowTextAtPoint(gc, 0, 0, str, strlen(str));
    CGPoint pt = CGContextGetTextPosition(gc);

    CGContextSetTextDrawingMode(gc, kCGTextFillStroke);

    CGContextShowTextAtPoint(gc, x - pt.x / 2, y, str, strlen(str));
}


- (void)drawRect:(CGRect)rect{

    CGContextRef theContext = UIGraphicsGetCurrentContext();
    CGRect viewBounds = self.bounds;

    CGContextTranslateCTM(theContext, 0, viewBounds.size.height);
    CGContextScaleCTM(theContext, 1, -1);

    CGContextSelectFont (theContext, "Helvetica", viewBounds.size.height,  kCGEncodingMacRoman);

    CGContextSetRGBFillColor (theContext, 1, 1, 1, 1);
    CGContextSetRGBStrokeColor (theContext, 0, 0, 0, 1);
    CGContextSetLineWidth(theContext, 1.0);

    ShowStringCentered(theContext, rect.size.width / 2.0, 12, [[self text] cStringUsingEncoding:NSASCIIStringEncoding]);
}

I just have a nagging feeling that I'm overlooking a simpler way to do this. Perhaps by overriding "drawTextInRect", but I can't seem to get drawTextInRect to bend to my will at all despite staring at it intently and frowning really really hard.

Axel Guilmin
  • 11,454
  • 9
  • 54
  • 64
Monte Hurd
  • 4,349
  • 5
  • 30
  • 35
  • Clarification - the slowness is apparent in my app 'cause I'm animating the label when its value changes with a slight grow and shrink. Without subclassing it's smooth, but with the code above the label animation is way choppy. Should I just use a UIWebView? I feel silly doing so as the label is only displaying a single number... – Monte Hurd Jul 09 '09 at 10:56
  • Ok it looks like the performance problem I was having was unrelated to the outline code, but I still can't seem to get it to vertically align. pt.y is always zero for some reason. – Monte Hurd Jul 10 '09 at 07:27
  • This is very slow for fonts like Chalkduster – jjxtra Aug 08 '13 at 16:18

15 Answers15

167

I was able to do it by overriding drawTextInRect:

- (void)drawTextInRect:(CGRect)rect {

  CGSize shadowOffset = self.shadowOffset;
  UIColor *textColor = self.textColor;

  CGContextRef c = UIGraphicsGetCurrentContext();
  CGContextSetLineWidth(c, 1);
  CGContextSetLineJoin(c, kCGLineJoinRound);

  CGContextSetTextDrawingMode(c, kCGTextStroke);
  self.textColor = [UIColor whiteColor];
  [super drawTextInRect:rect];

  CGContextSetTextDrawingMode(c, kCGTextFill);
  self.textColor = textColor;
  self.shadowOffset = CGSizeMake(0, 0);
  [super drawTextInRect:rect];

  self.shadowOffset = shadowOffset;

}
Community
  • 1
  • 1
kprevas
  • 2,452
  • 1
  • 17
  • 14
  • 3
    I tried this technique but the results are less than satisfying. On my iPhone 4 I tried using this code to draw white text with a black outline. What I got was black outlines on the left and right edges of each letter in the text but not outlines at the top or bottom. Any ideas? – Mike Hershberg Jan 26 '11 at 07:39
  • sorry I am trying use your code on my project but nothing happens ! see this pic :[link](http://www.i-phone.ir/forums/newuploads/e863ae8af3a68bb186b22ee3b4042454.png) – Mc.Lover Feb 18 '11 at 11:14
  • @Momeks: You have to subclass `UILabel` and put the `-drawTextInRect:` implementation there. – Jeff Kelley Feb 23 '11 at 16:12
  • @Jeff , Would you please help me more ? I don't know exactly Create subclass and put on dratext . is it right : `@interface CustomLabel : UILabel { } @end` – Mc.Lover Feb 23 '11 at 16:24
  • @Jeff and another thing how can use drawTextInrect with some uilabel outlet ? on different viewcontroller – Mc.Lover Feb 23 '11 at 16:39
  • 1
    @Momeks: If you don’t know how to make a subclass in Objective-C, you should do some Googling; Apple has a guide to the Objective-C language. – Jeff Kelley Feb 23 '11 at 16:43
  • @Jeff , Iam so close to solve this problem , i would appreciate if you answer my final question on this thread : [link](http://stackoverflow.com/questions/5093391/uilabel-with-outline-or-stroke) – Mc.Lover Feb 23 '11 at 16:51
  • Why not use 'CGContextSetTextDrawingMode(c, kCGTextFillStroke)' rather than 'CGContextSetTextDrawingMode(c, kCGTextStroke)'? Wouldn't that work with just one call to [super drawTextInRect:rect]? – Ricardo Sanchez-Saez Feb 13 '12 at 14:21
  • 1
    Probably - I don't think that existed when I wrote this 2.5 years ago :) – kprevas Feb 15 '12 at 19:31
  • To do this right without graphical artefacts you actually need to do a little more. I made a class that did this including soft shadows, clocking in at around 200 lines. For iOS6 I found a very sweet solution that let me replace that with a handful of lines. If anyone is interested I might put either in a blog post. – Nuoji Jul 13 '12 at 00:13
  • @morcutt here's the blog post: http://www.aegik.se/226/outlined-text-with-uilabel/ – Nuoji Jul 16 '12 at 08:29
  • You actually get nicer results if you "swap" drawing fill and stroke. Otherweise the stroke might be a little off... – Stefan Jul 12 '13 at 22:11
  • This is very slow for fonts like Chalkduster – jjxtra Aug 08 '13 at 16:19
  • How would you accomplish this on a UITextView? – bitops Jan 18 '15 at 23:02
  • Worked perfectly for me as-is on a UILabel. Thank you for the contribution! – Ron B. May 29 '19 at 00:45
128

A simpler solution is to use an Attributed String like so:

Swift 4:

let strokeTextAttributes: [NSAttributedStringKey : Any] = [
    NSAttributedStringKey.strokeColor : UIColor.black,
    NSAttributedStringKey.foregroundColor : UIColor.white,
    NSAttributedStringKey.strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

Swift 4.2:

let strokeTextAttributes: [NSAttributedString.Key : Any] = [
    .strokeColor : UIColor.black,
    .foregroundColor : UIColor.white,
    .strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

On a UITextField you can set the defaultTextAttributes and the attributedPlaceholder as well.

Note that the NSStrokeWidthAttributeName has to be negative in this case, i.e. only the inner outlines work.

UITextFields with an outline using attributed texts

Axel Guilmin
  • 11,454
  • 9
  • 54
  • 64
  • 18
    For convenience in Objective-C this will be: label.attributedText = [[NSAttributedString alloc] initWithString:@"String" attributes:@{ NSStrokeColorAttributeName : [UIColor blackColor], NSForegroundColorAttributeName : [UIColor whiteColor], NSStrokeWidthAttributeName : @-1.0 }]; – Leszek Szary Oct 04 '15 at 12:54
  • 12
    The use of this meme effectively communicated the nuance of this approach – woody121 Apr 05 '17 at 05:44
  • Swift 4.2: let strokeTextAttributes: [String : Any] = [ NSStrokeColorAttributeName : UIColor.black, NSForegroundColorAttributeName : UIColor.white, NSStrokeWidthAttributeName : -4.0, ] consoleView.typingAttributes = strokeTextAttributes – Teo Sartori Sep 10 '18 at 15:17
  • Thank you @TeoSartori, I added the Swift 4.2 syntax in my answer ;) – Axel Guilmin Sep 11 '18 at 08:29
28

After reading the accepted answer and the two corrections to it and the answer from Axel Guilmin, I decided to compile an overall solution in Swift, that suits me:

import UIKit

class UIOutlinedLabel: UILabel {

    var outlineWidth: CGFloat = 1
    var outlineColor: UIColor = UIColor.whiteColor()

    override func drawTextInRect(rect: CGRect) {

        let strokeTextAttributes = [
            NSStrokeColorAttributeName : outlineColor,
            NSStrokeWidthAttributeName : -1 * outlineWidth,
        ]

        self.attributedText = NSAttributedString(string: self.text ?? "", attributes: strokeTextAttributes)
        super.drawTextInRect(rect)
    }
}

You can add this custom UILabel class to an existing label in the Interface Builder and change the thickness of the border and its color by adding User Defined Runtime Attributes like this: Interface Builder setup

Result:

big red G with outline

Crashalot
  • 33,605
  • 61
  • 269
  • 439
Lachezar
  • 6,523
  • 3
  • 33
  • 34
10

There is one issue with the answer's implementation. Drawing a text with stroke has a slightly different character glyph width than drawing a text without stroke, which can produce "uncentered" results. It can be fixed by adding an invisible stroke around the fill text.

Replace:

CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

with:

CGContextSetTextDrawingMode(context, kCGTextFillStroke);
self.textColor = textColor;
[[UIColor clearColor] setStroke]; // invisible stroke
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

I'm not 100% sure, if that's the real deal, because I don't know if self.textColor = textColor; has the same effect as [textColor setFill], but it should work.

Disclosure: I'm the developer of THLabel.

I've released a UILabel subclass a while ago, which allows an outline in text and other effects. You can find it here: https://github.com/tobihagemann/THLabel

tobihagemann
  • 601
  • 8
  • 15
  • 4
    just used THLabel,life is complicated enough as it is – Prat Jun 04 '14 at 09:56
  • 1
    Thanks to THLabel, I was able to draw an outline around text by adding just two lines of code. Thanks for providing a solution to a problem I've been spending way too long trying to solve! – Alan Kinnaman Sep 04 '16 at 04:27
  • I'm surprised no one noticed, but the built-in text stroke command, does not create an *outline*, but instead it creates an "*inline*" - i.e. the stroke is drawn on the inside of the letters, not on the outside. With THLabel we can choose to have the stroke actually drawn on the outside. Great job! – lenooh Nov 04 '18 at 17:15
6

A Swift 4 class version based off the answer by kprevas

import Foundation
import UIKit

public class OutlinedText: UILabel{
    internal var mOutlineColor:UIColor?
    internal var mOutlineWidth:CGFloat?

    @IBInspectable var outlineColor: UIColor{
        get { return mOutlineColor ?? UIColor.clear }
        set { mOutlineColor = newValue }
    }

    @IBInspectable var outlineWidth: CGFloat{
        get { return mOutlineWidth ?? 0 }
        set { mOutlineWidth = newValue }
    }    

    override public func drawText(in rect: CGRect) {
        let shadowOffset = self.shadowOffset
        let textColor = self.textColor

        let c = UIGraphicsGetCurrentContext()
        c?.setLineWidth(outlineWidth)
        c?.setLineJoin(.round)
        c?.setTextDrawingMode(.stroke)
        self.textColor = mOutlineColor;
        super.drawText(in:rect)

        c?.setTextDrawingMode(.fill)
        self.textColor = textColor
        self.shadowOffset = CGSize(width: 0, height: 0)
        super.drawText(in:rect)

        self.shadowOffset = shadowOffset
    }
}

It can be implemented entirely in the Interface Builder by setting the UILabel's custom class to OutlinedText. You will then have the ability to set the outline's width and color from the Properties pane.

enter image description here

Chris Stillwell
  • 10,266
  • 10
  • 67
  • 77
5

This won't create an outline per-se, but it will put a shadow around the text, and if you make the shadow radius small enough it could resemble an outline.

label.layer.shadowColor = [[UIColor blackColor] CGColor];
label.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
label.layer.shadowOpacity = 1.0f;
label.layer.shadowRadius = 1.0f;

I don't know whether it is compatible with older versions of iOS..

Anyway, I hope it helps...

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
Suran
  • 1,209
  • 1
  • 17
  • 21
5

If your goal is something like this:

enter image description here

Here is how I achieved it: I added a new label of a custom class as a Subview to my current UILabel (inspired by this answer).

Just copy & paste it into your project and you are good to go:

extension UILabel {
    func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat) {
        class OutlinedText: UILabel{
            var outlineWidth: CGFloat = 0
            var outlineColor: UIColor = .clear

            override public func drawText(in rect: CGRect) {
                let shadowOffset = self.shadowOffset
                let textColor = self.textColor

                let c = UIGraphicsGetCurrentContext()
                c?.setLineWidth(outlineWidth)
                c?.setLineJoin(.round)
                c?.setTextDrawingMode(.stroke)
                self.textAlignment = .center
                self.textColor = outlineColor
                super.drawText(in:rect)

                c?.setTextDrawingMode(.fill)
                self.textColor = textColor
                self.shadowOffset = CGSize(width: 0, height: 0)
                super.drawText(in:rect)

                self.shadowOffset = shadowOffset
            }
        }

        let textOutline = OutlinedText()
        let outlineTag = 9999

        if let prevTextOutline = viewWithTag(outlineTag) {
            prevTextOutline.removeFromSuperview()
        }

        textOutline.outlineColor = outlineColor
        textOutline.outlineWidth = outlineWidth
        textOutline.textColor = textColor
        textOutline.font = font
        textOutline.text = text
        textOutline.tag = outlineTag

        sizeToFit()
        addSubview(textOutline)
        textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
                                   width: bounds.width + outlineWidth,
                                   height: bounds.height + outlineWidth)
    }
}

USAGE:

yourLabel.addTextOutline(usingColor: .red, outlineWidth: 6)

it also works for a UIButton with all its animations:

yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)
Gasper J.
  • 481
  • 5
  • 11
4

If you want to animate something complicated, the best way is to programmaticly take a screenshot of it an animate that instead!

To take a screenshot of a view, you'll need code a little like this:

UIGraphicsBeginImageContext(mainContentView.bounds.size);
[mainContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); 

Where mainContentView is the view you want to take a screenshot of. Add viewImage to a UIImageView and animate that.

Hope that speeds up your animation!!

N

Nick Cartwright
  • 8,334
  • 15
  • 45
  • 56
  • That's an awesome trick that I can think of a few places to use, and probably solves the performance issue, but i'm still holding out hope that there's a simpler way than my crappy subclass to make uilabel text show an outline. In the mean time I'm gonna play with your example thanks! – Monte Hurd Jul 09 '09 at 11:08
  • I feel your pain. I'm not sure you'll find a good way for doing that. I often write reams of code to product effects then snapshot them (like above) for smooth animation! For the example above you need to include in your code! Best of luck! N – Nick Cartwright Jul 09 '09 at 11:18
4

As MuscleRumble mentioned, the accepted answer's border is a bit off center. I was able to correct this by setting the stroke width to zero instead of changing the color to clear.

i.e. replacing:

CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

with:

CGContextSetTextDrawingMode(c, kCGTextFillStroke);
self.textColor = textColor;
CGContextSetLineWidth(c, 0); // set stroke width to zero
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

I would've just commented on his answer but apparently I'm not "reputable" enough.

Community
  • 1
  • 1
ufmike
  • 93
  • 8
2

if ALL you want is a one pixel black border around my white UILabel text,

then i do think you're making the problem harder than it is... I don't know by memory which 'draw rect / frameRect' function you should use, but it will be easy for you to find. this method just demonstrates the strategy (let the superclass do the work!):

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
kent
  • 6,286
  • 4
  • 27
  • 32
  • i don't get it. probably because i've been staring at the screen for about 12 hours and i don't get much at this point... – Monte Hurd Jul 09 '09 at 11:43
  • you are subclassing UILabel, a class which already draws text. and the reason you are subclassing is because you want to add a black border around the text. so if you let the superclass draw the text, then all you have to do is draw the border, no? – kent Jul 09 '09 at 11:51
  • This is a good point... although, if he wants to add a 1 pixel black border, the subclass will probably draw the letters too close together! ....I'd do something like posted in the original question and optimise (as stated in my post)! N – Nick Cartwright Jul 09 '09 at 12:06
  • @nickcartwright: if the case occurs that the superclass is doing as you say, you could inset the CGRect before calling [super drawRect]. still easier and safer than re-writing the functionality of the superclass. – kent Jul 09 '09 at 14:07
  • And then @kent left SO in frustration. Great answer, +1. – Dan Rosenstark Jul 12 '10 at 19:35
1

I found an issue with the main answer. The text position is not necessarily centered correctly to sub-pixel location, so that the outline can be mismatched around the text. I fixed it using the following code, which uses CGContextSetShouldSubpixelQuantizeFonts(ctx, false):

- (void)drawTextInRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    [self.textOutlineColor setStroke];
    [self.textColor setFill];

    CGContextSetShouldSubpixelQuantizeFonts(ctx, false);

    CGContextSetLineWidth(ctx, self.textOutlineWidth);
    CGContextSetLineJoin(ctx, kCGLineJoinRound);

    CGContextSetTextDrawingMode(ctx, kCGTextStroke);
    [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];

    CGContextSetTextDrawingMode(ctx, kCGTextFill);
    [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
}

This assumes that you defined textOutlineColor and textOutlineWidth as properties.

Robotbugs
  • 4,307
  • 3
  • 22
  • 30
0

Here is the another answer to set outlined text on label.

extension UILabel {

func setOutLinedText(_ text: String) {
    let attribute : [NSAttributedString.Key : Any] = [
        NSAttributedString.Key.strokeColor : UIColor.black,
        NSAttributedString.Key.foregroundColor : UIColor.white,
        NSAttributedString.Key.strokeWidth : -2.0,
        NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12)
        ] as [NSAttributedString.Key  : Any]

    let customizedText = NSMutableAttributedString(string: text,
                                                   attributes: attribute)
    attributedText = customizedText
 }

}

set outlined text simply using the extension method.

lblTitle.setOutLinedText("Enter your email address or username")
Aman Pathak
  • 219
  • 2
  • 6
0

it is also possible to subclass UILabel with the following logic:

- (void)setText:(NSString *)text {
    [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:text]];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    [self addOutlineForAttributedText:attributedText];
}

- (void)addOutlineForAttributedText:(NSAttributedString *)attributedText {
    NSDictionary *strokeTextAttributes = @{
                                           NSStrokeColorAttributeName: [UIColor blackColor],
                                           NSStrokeWidthAttributeName : @(-2)
                                           };

    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
    [attrStr addAttributes:strokeTextAttributes range:NSMakeRange(0, attrStr.length)];

    super.attributedText = attrStr;
}

and if you set text in Storyboard then:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];
    if (self) {
        // to apply border for text from storyboard
        [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:self.text]];
    }
    return self;
}
dollar2048
  • 426
  • 6
  • 10
-3

Why don't you create a 1px border UIView in Photoshop, then set a UIView with the image, and position it behind your UILabel?

Code:

UIView *myView;
UIImage *imageName = [UIImage imageNamed:@"1pxBorderImage.png"];
UIColor *tempColour = [[UIColor alloc] initWithPatternImage:imageName];
myView.backgroundColor = tempColour;
[tempColour release];

It's going to save you subclassing an object and it's fairly simple to do.

Not to mention if you want to do animation, it's built into the UIView class.

Brock Woolf
  • 46,656
  • 50
  • 121
  • 144
  • The text on the label changes and I need the text itself to have a 1 pixel black outline. (The rest of the UILabel's background is transparent.) – Monte Hurd Jul 09 '09 at 11:02
  • I thought I understood how this will cause any text I place in the UILabel to be outlined, but I was deliriously tired and now I can't seem to wrap my mind around how it could accomplish that. I'm going to go read up on initWithPatternImage. – Monte Hurd Jul 10 '09 at 07:29
-3

To put a border with rounded edges around a UILabel I do the following:

labelName.layer.borderWidth = 1;
labelName.layer.borderColor = [[UIColor grayColor] CGColor];
labelName.layer.cornerRadius = 10;

(don't forget to include QuartzCore/QuartzCore.h)

mike
  • 5