Change character spacing on UILabel within Interface Builder

IosSwiftUilabelInterface Builder

Ios Problem Overview


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?

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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.

Solution 3 - Ios

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)

Solution 4 - Ios

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

Solution 5 - Ios

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
    }
  }
}

Solution 6 - Ios

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

Solution 7 - Ios

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)
        }
    }
}

Solution 8 - Ios

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
            }
        }
    }
}

Solution 9 - Ios

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
    }
}

Solution 10 - Ios

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
    }
}

Solution 11 - Ios

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
        }
    }
}

Solution 12 - Ios

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
    }
}

Solution 13 - Ios

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

Solution 14 - Ios

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
    }
  }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncodemanView Question on Stackoverflow
Solution 1 - IosbudiDinoView Answer on Stackoverflow
Solution 2 - IoscodemanView Answer on Stackoverflow
Solution 3 - Iosluk2302View Answer on Stackoverflow
Solution 4 - IosMike GlukhovView Answer on Stackoverflow
Solution 5 - IosFredFlinstoneView Answer on Stackoverflow
Solution 6 - IosBeslan TularovView Answer on Stackoverflow
Solution 7 - IosAdam SmakaView Answer on Stackoverflow
Solution 8 - IosMobile DeveloperView Answer on Stackoverflow
Solution 9 - IosMax PaulsonView Answer on Stackoverflow
Solution 10 - IosSoheil NovinfardView Answer on Stackoverflow
Solution 11 - IosYuri BikovskyView Answer on Stackoverflow
Solution 12 - IosUmairView Answer on Stackoverflow
Solution 13 - IosKrunalView Answer on Stackoverflow
Solution 14 - IosDavid KyslenkoView Answer on Stackoverflow