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?
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.
NSMakeRange(0, attributedString.length)
to NSMakeRange(0, attributedString.length - 1)
. Source –
Sleuth max(0, attributedString.length - 1)
will help. –
Kane 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
.
if let textString = text, textString.length > 0
to catch index out of bounds when the string is "" (empty). –
Roley attributedText
properties –
Docila 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.
NSMakeRange(0, attributedString.length)
to NSMakeRange(0, attributedString.length - 1)
. Source –
Sleuth max(0, attributedString.length - 1)
will help. –
Kane 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.
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)
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
}
}
}
}
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
}
}
}
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
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
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)
}
}
}
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
}
}
}
}
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
}
}
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
}
}
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
}
}
}
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
}
}
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
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
}
}
}
© 2022 - 2024 — McMap. All rights reserved.