A.Solodky
A.Solodky

Reputation: 171

How to change letter spacing of UIButton in Swift?

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

Upvotes: 13

Views: 12452

Answers (9)

Jayaraj
Jayaraj

Reputation: 342

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)

Upvotes: 19

Mayur Rathod
Mayur Rathod

Reputation: 394

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)

Upvotes: 0

Li Jin
Li Jin

Reputation: 2147

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

Upvotes: 2

Zaheer Moola
Zaheer Moola

Reputation: 133

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

Upvotes: 0

Levi Joraanstad
Levi Joraanstad

Reputation: 33

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

Upvotes: 1

Womble
Womble

Reputation: 5290

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

Upvotes: 2

Max
Max

Reputation: 646

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)

Upvotes: 9

hhamm
hhamm

Reputation: 1581

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

Upvotes: 1

Code Different
Code Different

Reputation: 93161

  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)

Upvotes: 14

Related Questions