Reputation: 171
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
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
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
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
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
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
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:
fixed:
[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
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
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
Reputation: 93161
NSAttributedString
like in the question you linkedsetAttributedTitle(_ ,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