How to change letter spacing of UIButton in Swift?
Asked Answered
A

9

13

I've found how to set letter spacing to UILabel (here) but this method is not working for UIButtons. Does anyone know how to do it?

Here is the code i'm using

    let buttonString = agreementButton.attributedTitleForState(.Normal) as! NSMutableAttributedString
    buttonString.addAttribute(NSKernAttributeName, value: 1.0, range: NSMakeRange(0, buttonString.length))
    agreementButton.setAttributedTitle(buttonString, forState: .Normal)

It throws me an error: 'NSConcreteAttributedString' (0x19e508660) to 'NSMutableAttributedString' (0x19e506a40).

Almita answered 2/12, 2015 at 17:12 Comment(2)
Where exactly is the problem? Please post the code that is not working for you.Susi
Updated for you to see the broken code.Almita
R
14
  1. Make the NSAttributedString like in the question you linked
  2. Call setAttributedTitle(_ ,forState:) on your UIButton

Try this (untested):

let title = agreementButton.titleForState(.Normal)
let attributedTitle = NSAttributedString(string: title, attributes: [NSKernAttributeName: 1.0])
agreementButton.setAttributedTitle(attributedTitle, forState: .Normal)
Referent answered 2/12, 2015 at 18:17 Comment(1)
My bad. Should have been just setAttributedTitle. EditedReferent
D
19

Swift 5.0

extension UIButton{
   func addTextSpacing(_ spacing: CGFloat){
       let attributedString = NSMutableAttributedString(string: title(for: .normal) ?? "")
       attributedString.addAttribute(NSAttributedString.Key.kern, value: spacing, range: NSRange(location: 0, length: attributedString.string.count))
       self.setAttributedTitle(attributedString, for: .normal)
   }
}
button.addTextSpacing(1)

extension UILabel{
    func addTextSpacing(_ spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.text ?? "")
        attributedString.addAttribute(NSAttributedString.Key.kern, value: spacing, range: NSRange(location: 0, length: attributedString.string.count))
        self.attributedText = attributedString
    }
}
label.addTextSpacing(1)

extension UITextField{
    func addPlaceholderSpacing(_ spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.placeholder ?? "")
        attributedString.addAttribute(NSAttributedString.Key.kern, value: spacing, range: NSRange(location: 0, length: attributedString.string.count))
        self.attributedPlaceholder = attributedString
    }
}
textField.addPlaceholderSpacing(1)

extension UINavigationItem{
    func addSpacing(_ spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.title ?? "")
        attributedString.addAttribute(NSAttributedString.Key.kern, value: spacing, range: NSRange(location: 0, length: attributedString.string.count))
        let label = UILabel()
        label.textColor = UIColor.black
        label.font = UIFont.systemFont(ofSize: 15, weight: .bold)
        label.attributedText = attributedString
        label.sizeToFit()
        self.titleView = label
    }
}
navigationItem.addSpacing(1)
Deportation answered 16/11, 2016 at 12:18 Comment(0)
R
14
  1. Make the NSAttributedString like in the question you linked
  2. Call setAttributedTitle(_ ,forState:) on your UIButton

Try this (untested):

let title = agreementButton.titleForState(.Normal)
let attributedTitle = NSAttributedString(string: title, attributes: [NSKernAttributeName: 1.0])
agreementButton.setAttributedTitle(attributedTitle, forState: .Normal)
Referent answered 2/12, 2015 at 18:17 Comment(1)
My bad. Should have been just setAttributedTitle. EditedReferent
R
9

Swift 4

extension UIButton{

    func addTextSpacing(_ letterSpacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: (self.titleLabel?.text!)!)
        attributedString.addAttribute(NSAttributedString.Key.kern, value: letterSpacing, range: NSRange(location: 0, length: (self.titleLabel?.text!.count)!))
        self.setAttributedTitle(attributedString, for: .normal)
    }

}

// Usage: button.addTextSpacing(5.0)
Rickrack answered 2/9, 2018 at 11:47 Comment(1)
Works great! "NSAttributedStringKey" now should be "NSAttributedString.Key".Visionary
C
2

Not a full answer, but a GOTCHA, and a FIX.

GOTCHA: applying character spacing also adds the space to the END of the text. This misfeature/bug means that center-aligned text will appear incorrectly.

FIX: when creating a Range for the attributed text, subtract 1 from the text.count value (thus ignoring the last character in the string for spacing purposes.)

e.g. incorrect centering due to extra space:

enter image description here

fixed:

enter image description here

[edited]

On a related note, if you are using EdgeInsets to impose padding around the text, your UIButton subclass will need to override intrinsicContentsSize:

override open var intrinsicContentSize: CGSize {
    let size = super.intrinsicContentSize
    let insets = self.titleEdgeInsets
    let width = size.width + insets.left + insets.right
    let height = size.height + insets.top + insets.bottom
    return CGSize(width: width, height: height)
}
Contrecoup answered 2/5, 2019 at 1:55 Comment(0)
W
2

Swift 5 Extension goes here.

extension UIButton {
    @IBInspectable
    var letterSpacing: CGFloat {
        set {
            let attributedString: NSMutableAttributedString
            if let currentAttrString = attributedTitle(for: .normal) {
                attributedString = NSMutableAttributedString(attributedString: currentAttrString)
            }
            else {
                attributedString = NSMutableAttributedString(string: self.title(for: .normal) ?? "")
                setTitle(.none, for: .normal)
            }

            attributedString.addAttribute(NSAttributedString.Key.kern, value: newValue, range: NSRange(location: 0, length: attributedString.length))
            setAttributedTitle(attributedString, for: .normal)
        }
        get {
            if let currentLetterSpace = attributedTitle(for: .normal)?.attribute(NSAttributedString.Key.kern, at: 0, effectiveRange: .none) as? CGFloat {
                return currentLetterSpace
            }
            else {
                return 0
            }
        }
    }
}

Usage: You can set the letter space on storyboard or by code.

button.letterSpacing = 2.0
Walrus answered 23/9, 2020 at 20:2 Comment(0)
S
1

The solution from Code Different doesn't respect text color settings. Also one could override the UIButton class to have the spacing parameter available even in the storyboard. Here comes an updated Swift 3 solution:

Swift 3

class UIButtonWithSpacing : UIButton
{
    override func setTitle(_ title: String?, for state: UIControlState)
    {
        if let title = title, spacing != 0
        {
            let color = super.titleColor(for: state) ?? UIColor.black
            let attributedTitle = NSAttributedString(
                string: title,
                attributes: [NSKernAttributeName: spacing,
                             NSForegroundColorAttributeName: color])
            super.setAttributedTitle(attributedTitle, for: state)
        }
        else
        {
            super.setTitle(title, for: state)
        }
    }

    fileprivate func updateTitleLabel_()
    {
        let states:[UIControlState] = [.normal, .highlighted, .selected, .disabled]
        for state in states
        {
            let currentText = super.title(for: state)
            self.setTitle(currentText, for: state)
        }
    }

    @IBInspectable var spacing:CGFloat = 0 {
        didSet {
            updateTitleLabel_()
        }
    }
}
Serrano answered 3/11, 2016 at 14:47 Comment(0)
R
1

Update for Swift 4 based off jaya raj's answer.

extension UIButton{
    func addTextSpacing(spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: (self.titleLabel?.text!)!)
        attributedString.addAttribute(NSAttributedStringKey.kern, value: spacing, range: NSRange(location: 0, length: (self.titleLabel?.text!.characters.count)!))
        self.setAttributedTitle(attributedString, for: .normal)
    }
}

extension UILabel{
    func addTextSpacing(spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.text!)
        attributedString.addAttribute(NSAttributedStringKey.kern, value: spacing, range: NSRange(location: 0, length: self.text!.characters.count))
        self.attributedText = attributedString
    }
}

extension UITextField{
    func addPlaceholderSpacing(spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.placeholder!)
        attributedString.addAttribute(NSAttributedStringKey.kern, value: spacing, range: NSRange(location: 0, length: self.placeholder!.characters.count))
        self.attributedPlaceholder = attributedString
    }
}

extension UINavigationItem{
    func addSpacing(spacing: CGFloat){
        let attributedString = NSMutableAttributedString(string: self.title!)
        attributedString.addAttribute(NSAttributedStringKey.kern, value: spacing, range: NSRange(location: 0, length: self.title!.characters.count))
        let label = UILabel()
        label.textColor = UIColor.black
        label.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.bold)
        label.attributedText = attributedString
        label.sizeToFit()
        self.titleView = label
    }
}
Ruff answered 13/8, 2019 at 15:37 Comment(0)
B
0

Update for Swift 5 without forced unwrapping:

I feel this is a better solution as we cater for existing attributes on the button as well

func addTextSpacing(_ letterSpacing: CGFloat) {
    let attributedString = attributedTitle(for: .normal)?.mutableCopy() as? NSMutableAttributedString ??
        NSMutableAttributedString(string: title(for: .normal) ?? "")
    attributedString.addAttribute(NSAttributedString.Key.kern, value: letterSpacing,
                                  range: NSRange(location: 0, length: attributedString.string.count))
    self.setAttributedTitle(attributedString, for: .normal)
}
Beaudoin answered 27/7, 2020 at 18:58 Comment(0)
F
0

Swift 5

guard let buttonTitle = button.title(for: .normal) else { return }
    
    let attributedTitle = NSAttributedString(string: buttonTitle, attributes: [NSAttributedString.Key.kern: kernValue])
    button.setAttributedTitle(attributedTitle, for: .normal)
Fredella answered 17/5, 2021 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.