Change character spacing on UILabel within Interface Builder
Asked Answered
M

14

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?

Marko answered 17/12, 2014 at 22:18 Comment(2)
I'm 99% sure Xcode 6 does not support this.Clino
@Clino i'm 99% sure Xcode 6 can do IBDesignablesPhysiography
M
48

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.

Marko answered 17/12, 2014 at 23:1 Comment(2)
+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). SourceSleuth
@Sleuth is correct. You also need to validate the empty string to avoid crash. Simply add max(0, attributedString.length - 1) will help.Kane
K
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.

Knisley answered 24/4, 2016 at 11:32 Comment(7)
is it possible to change character spacing ?Vibrato
@Priyal, there is a 1.15 spacing value in there... just change it to a bigger number if you want more spacingKnisley
@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 ?Vibrato
@budidino Thanks! Is it equivalent to reducing lateral spacing ?Vibrato
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).Roley
I think this solution overwrites any existing attributedText propertiesDocila
@Docila so run it firstLeonidaleonidas
M
48

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.

Marko answered 17/12, 2014 at 23:1 Comment(2)
+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). SourceSleuth
@Sleuth is correct. You also need to validate the empty string to avoid crash. Simply add max(0, attributedString.length - 1) will help.Kane
H
25

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)

Hosmer answered 25/3, 2015 at 18:47 Comment(8)
Hah not a bad solution. Hadn't thought of that.Marko
Thanks, the idea just popped into my head and i had to laugh myself because it is quite a cheesy solution ^^Hosmer
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 :)Tynan
@Tynan 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 ;)Hosmer
Yes definitely and really thank you for the original idea. You saved me a lot of time trying to do NSAttributedString in code.Tynan
hi Luk - I truly love you but this is truly a bad idea. I'm sorry man!Cabstand
Think of localization for example... it is going to mess it upToliver
This is a really bad idea for accessibility. If you use this be sure to set the accessibility label to the real text.Stefaniestefano
A
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

Augustina answered 29/11, 2017 at 9:16 Comment(4)
it works Perfectly. seems i was missing a part in my code . SorryTacye
this doesn't support custom fontFleenor
Totally perfect way. I also added the same code for UILabel and UIButton and they also work. Just get text from titleLabel.text and titleLabel.attributedText for UIButtonPapandreou
Great solution, couple of code updates for things that have been updated in later versions of Xcode, but Xcode handles all the fixes for you. Thanks!Thiazine
C
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
    }
  }
}
Costanza answered 9/7, 2017 at 11:13 Comment(1)
@Dwight he edit his answer, before there was no default value. I'll edit mine too, thx.Costanza
M
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://mcmap.net/q/281016/-adjust-letter-spacing-in-ios-7

Mascara answered 17/12, 2014 at 23:0 Comment(2)
beautiful - wouldn't you use IBDesignables here?Cabstand
but this question is about letter spacing, not line spacingPorker
P
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)
        }
    }
}
Pennebaker answered 25/10, 2017 at 9:37 Comment(1)
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 :)Knisley
S
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
            }
        }
    }
}
Slipcase answered 1/3, 2019 at 14:30 Comment(0)
P
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
    }
}
Plot answered 20/9, 2018 at 3:8 Comment(0)
D
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
    }
}
Dammar answered 4/6, 2019 at 14:58 Comment(0)
R
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
        }
    }
}
Reprint answered 30/1, 2021 at 10:42 Comment(1)
Please note, you must call label.text = "some text" from code to make it work :))Reprint
C
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
    }
}
Carencarena answered 19/4, 2018 at 20:38 Comment(0)
C
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
Cannes answered 28/9, 2017 at 13:38 Comment(0)
A
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
    }
  }
}
Adrenaline answered 21/11, 2020 at 12:56 Comment(2)
How is this different from @budidino's answer?Dwight
@Dwight 'NSAttributedStringKey' has been renamed to 'NSAttributedString.Key'Adrenaline

© 2022 - 2024 — McMap. All rights reserved.