70

Is there anyway to change the character spacing (track) on UILabel text using Interface Builder? If not, is there a way to do it programmatically on an existing UILabel that was already created with attributed text?

LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
codeman
  • 8,868
  • 13
  • 50
  • 79

14 Answers14

120

I know it's not an Interface Builder solution, but you can create a UILabel extension and then add spacing to any UILabel you want:

extension UILabel {
  func addCharacterSpacing(kernValue: Double = 1.15) {
    guard let text = text, !text.isEmpty else { return }
    let string = NSMutableAttributedString(string: text)
    string.addAttribute(NSAttributedString.Key.kern, value: kernValue, range: NSRange(location: 0, length: string.length - 1))
    attributedText = string
  }
}

Consider changing the default kernValue from 1.15 to something that works better with your design.


When implementing always add character spacing after setting the text value:

myLabel.text = "We used to be so close"
myLabel.addCharacterSpacing()

If you plan to have different spacing at different places in the app, you can override the default kern value:

myLabelWithSpecialNeeds.addCharacterSpacing(kernValue: 1.3)

This solution overrides any other attributes you might have on your UILabel's attributedText.

budiDino
  • 13,044
  • 8
  • 95
  • 91
  • is it possible to change character spacing ? – Priyal May 03 '17 at 10:10
  • @Priyal, there is a 1.15 spacing value in there... just change it to a bigger number if you want more spacing – budiDino May 03 '17 at 14:51
  • @budidino as I can infer from example you have increased spacing between words but I want to change character spacing, ie. between We & user or used & to.... but I want to manage spacing between 'W' and 'e' of We, 'u', 's','e' and 'r' of user. Is it possible ? – Priyal May 03 '17 at 14:59
  • @budidino Thanks! Is it equivalent to reducing lateral spacing ? – Priyal May 04 '17 at 05:36
  • 2
    Very nice answer! Little addition: you may change the if clause to `if let textString = text, textString.length > 0` to **catch index out of bounds** when the string is "" (empty). – inf1783 Jul 31 '17 at 09:06
  • I think this solution overwrites any existing `attributedText` properties – tomblah Jan 10 '20 at 03:24
  • @tomblah so run it first –  Mar 03 '20 at 15:15
47

Ended up using this for now to get existing attributed text and modify to add character spacing:

let attributedString = discoveryTitle.attributedText as NSMutableAttributedString
attributedString.addAttribute(NSKernAttributeName, value: 1.0, range: NSMakeRange(0, attributedString.length))
discoveryTitle.attributedText = attributedString

Swift 3:

let attributedString = NSMutableAttributedString(string: discoveryTitle.text)
attributedString.addAttribute(NSKernAttributeName, value: CGFloat(1.0), range: NSRange(location: 0, length: attributedString.length))
discoveryTitle.attributedText = attributedString

Using NSRange instead of NSMakeRange works in Swift 3.

Slavcho
  • 2,792
  • 3
  • 30
  • 48
codeman
  • 8,868
  • 13
  • 50
  • 79
  • 9
    +1. Awesome answer. If your text must remain centred, apply the kerning to all but the last character, i.e. change `NSMakeRange(0, attributedString.length)` to `NSMakeRange(0, attributedString.length - 1)`. [Source](http://stackoverflow.com/a/23743928/1305067) – paulvs Jul 03 '16 at 22:22
  • @paulvs is correct. You also need to validate the empty string to avoid crash. Simply add `max(0, attributedString.length - 1)` will help. – nahung89 Oct 08 '21 at 05:35
24

For completely static text, like the header of a view or especially the launchScreen, you can insert letters that take up a tiny amount of width (e.g. the 'l' character) with 0 opacity. Alternatively set its color to the same as background.

I am aware of the fact, that is not the prettiest solution, but it is the only solution that works without writing any code and does the job - until you can do it by specifying the attributes in Xcode.

The result How to

Edit / Additional idea: To make your spacing even more variable you can change the font size of the filling charachters in between. (Thanks to @mohamede1945 for that idea)

luk2302
  • 55,258
  • 23
  • 97
  • 137
  • Hah not a bad solution. Hadn't thought of that. – codeman Mar 25 '15 at 22:56
  • Thanks, the idea just popped into my head and i had to laugh myself because it is quite a cheesy solution ^^ – luk2302 Mar 25 '15 at 22:58
  • 1
    Awesome solution! :) you can even control the width with the font size of the '|' character. All you need is just create one and copy/paste it :) – mohamede1945 Jun 17 '15 at 02:01
  • 3
    @mohamede1945 nice idea, before i actually read you comment and read through my answer again (was given quite a while back) I also got the idea to maybe change the font size as well - then read your comment... if two people independently come up with the same idea, it has to be good ;) – luk2302 Jun 17 '15 at 08:05
  • Yes definitely and really thank you for the original idea. You saved me a lot of time trying to do NSAttributedString in code. – mohamede1945 Jun 18 '15 at 02:24
  • 5
    hi Luk - I truly love you but this is truly a bad idea. I'm sorry man! – Fattie Sep 25 '15 at 15:58
  • 2
    Think of localization for example... it is going to mess it up – Rasto Nov 15 '16 at 23:43
  • 2
    This is a really bad idea for accessibility. If you use this be sure to set the accessibility label to the real text. – Sami Samhuri Dec 14 '17 at 21:11
22

Swift 3.2 & Interface builder

extension UILabel {

    @IBInspectable
    var letterSpace: CGFloat {
        set {
            let attributedString: NSMutableAttributedString!
            if let currentAttrString = attributedText {
                attributedString = NSMutableAttributedString(attributedString: currentAttrString)
            }
            else {
                attributedString = NSMutableAttributedString(string: text ?? "")
                text = nil
            }

            attributedString.addAttribute(NSKernAttributeName,
                                           value: newValue,
                                           range: NSRange(location: 0, length: attributedString.length))

            attributedText = attributedString
        }

        get {
            if let currentLetterSpace = attributedText?.attribute(NSKernAttributeName, at: 0, effectiveRange: .none) as? CGFloat {
                return currentLetterSpace
            }
            else {
                return 0
            }
        }
    }
}

enter image description here

Mike Glukhov
  • 1,758
  • 19
  • 18
13

Swift 5 and higher

extension UILabel {
  func setTextSpacingBy(value: Double) {
    if let textString = self.text {
      let attributedString = NSMutableAttributedString(string: textString)
      attributedString.addAttribute(NSKernAttributeName, value: value, range: NSRange(location: 0, length: attributedString.length - 1))
      attributedText = attributedString
    }
  }
}
FredFlinstone
  • 896
  • 11
  • 16
8

try this!!

create CustomLabel class

@interface CustomLabel : UILabel
@property (assign, nonatomic) CGFloat myLineSpacing;
@end


@implementation CustomLabel

- (void)setMyLineSpacing:(CGFloat)myLineSpacing {
    _myLineSpacing = myLineSpacing;
    self.text = self.text;
}

- (void)setText:(NSString *)text {
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.lineSpacing = _myLineSpacing;
    paragraphStyle.alignment = self.textAlignment;
    NSDictionary *attributes = @{NSParagraphStyleAttributeName: paragraphStyle};
    NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text
                                                                         attributes:attributes];
    self.attributedText = attributedText;
}

and set runtime attribute

enter image description here

Note this is actually line spacing (also called leading .. in the very old days (pre-digital) you'd put lead (the metal) between lines to increase the gap between lines. For spacing between letters, that is called kerning .. here's how to do kerning https://stackoverflow.com/a/21141156/294884

Community
  • 1
  • 1
Beslan Tularov
  • 3,111
  • 1
  • 21
  • 34
7

Why all of you are defining NSMUTABLEAttributedString. You don't have to set range explicitly. It makes emojis looks weird sometimes. This is my solution, tested in Swift 4.

extension UILabel {
    func addCharactersSpacing(_ value: CGFloat = 1.15) {
        if let textString = text {
            let attrs: [NSAttributedStringKey : Any] = [.kern: value]
            attributedText = NSAttributedString(string: textString, attributes: attrs)
        }
    }
}
Adam Smaka
  • 5,977
  • 3
  • 50
  • 55
  • If you don't specify the range, center and right aligned texts will look off the mark because there will be added spacing after the last character. I might be completely wrong though :) – budiDino Nov 06 '17 at 11:58
6

SWIFT 4 UILabel extension:

import UIKit

extension UILabel {

    @IBInspectable
    var letterSpace: CGFloat {
        set {
            let attributedString: NSMutableAttributedString!
            if let currentAttrString = attributedText {
                attributedString = NSMutableAttributedString(attributedString: currentAttrString)
            } else {
                attributedString = NSMutableAttributedString(string: text ?? "")
                text = nil
            } 
            attributedString.addAttribute(NSAttributedString.Key.kern,
                                          value: newValue,
                                          range: NSRange(location: 0, length: attributedString.length))
            attributedText = attributedString
        }

        get {
            if let currentLetterSpace = attributedText?.attribute(NSAttributedString.Key.kern, at: 0, effectiveRange: .none) as? CGFloat {
                return currentLetterSpace
            } else {
                return 0
            }
        }
    }
}
Mobile Developer
  • 5,730
  • 1
  • 39
  • 45
3

Here is a solution for Swift 4 that won't override existing text attributes:

extension UILabel {

    /**
     Add kerning to a UILabel's existing `attributedText`
     - note: If `UILabel.attributedText` has not been set, the `UILabel.text`
     value will be returned from `attributedText` by default
     - note: This method must be called each time `UILabel.text` or
     `UILabel.attributedText` has been set
     - parameter kernValue: The value of the kerning to add
     */
    func addKern(_ kernValue: CGFloat) {
        guard let attributedText = attributedText,
            attributedText.string.count > 0,
            let fullRange = attributedText.string.range(of: attributedText.string) else {
                return
        }
        let updatedText = NSMutableAttributedString(attributedString: attributedText)
        updatedText.addAttributes([
            .kern: kernValue
            ], range: NSRange(fullRange, in: attributedText.string))
        self.attributedText = updatedText
    }
}
3

You can use the following Swift 4 UILabel extension which considers both existing attributed text and plain text in order to do not override the existing settings:

import UIKit

extension UILabel {
    func addCharacterSpacing(_ kernValue: Double = 1.30) {
        guard let attributedString: NSMutableAttributedString = {
            if let text = self.text, !text.isEmpty {
                return NSMutableAttributedString(string: text)
            } else if let attributedText = self.attributedText {
                return NSMutableAttributedString(attributedString: attributedText)
            }
            return nil
            }() else { return}

        attributedString.addAttribute(
            NSAttributedString.Key.kern,
            value: kernValue,
            range: NSRange(location: 0, length: attributedString.length)
        )
        self.attributedText = attributedString
    }
}
Soheil Novinfard
  • 1,358
  • 1
  • 16
  • 43
3

Here is my code for letter spacing. Create custom label class and set letter spacing from storyboard. Swift 5.

import UIKit

class SpacingLabel: UILabel {

    @IBInspectable
    var letterSpacing: Double = 0

    override public var text: String? {
        didSet {
            self.addCharacterSpacing(letterSpacing)
        }
    }

    func addCharacterSpacing(_ kernValue: Double) {
        if let labelText = text, labelText.count > 0 {
            let attributedString = NSMutableAttributedString(string: labelText)
            attributedString.addAttribute(NSAttributedString.Key.kern, value: kernValue, range: NSRange(location: 0, length: attributedString.length - 1))
            attributedText = attributedString
        }
    }
}
Witek Bobrowski
  • 3,749
  • 1
  • 20
  • 34
2

Try this. It will add the character spacing you assign, either you set simple text or attributed text.

open class UHBCustomLabel : UILabel {
    @IBInspectable open var characterSpacing:CGFloat = 1 {
        didSet {
            updateWithSpacing()
        }

    }

    open override var text: String? {
        set {
            super.text = newValue
            updateWithSpacing()
        }
        get {
            return super.text
        }
    }
    open override var attributedText: NSAttributedString? {
        set {
            super.attributedText = newValue
            updateWithSpacing()     
        }
        get {
            return super.attributedText
        }
    }
    func updateWithSpacing() {
        let attributedString = self.attributedText == nil ? NSMutableAttributedString(string: self.text ?? "") : NSMutableAttributedString(attributedString: attributedText!)
        attributedString.addAttribute(NSKernAttributeName, value: self.characterSpacing, range: NSRange(location: 0, length: attributedString.length))
        super.attributedText = attributedString
    }
}
Umair
  • 1,203
  • 13
  • 15
0

Programming approach. (Try this, it should work for you)
Note: I tested in Swift 4

let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40

// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))

// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))

label.attributedText = attrString
Krunal
  • 77,632
  • 48
  • 245
  • 261
0

Swift 5.3.1

extension UILabel {
  func addCharacterSpacing(kernValue: Double = 1.15) {
    if let labelText = text, labelText.count > 0 {
      let attributedString = NSMutableAttributedString(string: labelText)
        attributedString.addAttribute(NSAttributedString.Key.kern, value: kernValue, range: NSRange(location: 0, length: attributedString.length - 1))
      attributedText = attributedString
    }
  }
}
David
  • 857
  • 1
  • 11
  • 25