Change the space between Letters

Dears,


Is it possible to change the space between letters while using UILabel?

I would like to expand it a little bit... for example:


As is: EXAMPLE
I would like to have it like: E X A M P L E


How can I do it?

Thanks

Answered by OOPer in 333317022

Themes.swift


First of all, you have no need to enclose two structs inside a class.

Second, if you want to apply extra spacing using `kern` (though I'm not sure if it is the best way for you) only for menuOption,

`LabelTheme` needs to be modified, just touching a very limited part of `LabelContent` is not sufficient.


import UIKit

struct LabelTheme {
    let color: UIColor
    let font: UIFont
    let extraAttributes: [NSAttributedStringKey: Any]?
    
    init(color: UIColor, font: UIFont, extraAttributes: [NSAttributedStringKey: Any]? = nil) {
        self.color = color
        self.font = font
        self.extraAttributes = extraAttributes
    }
    
    static let menuOption = LabelTheme(color: .red, font: UIFont.boldSystemFont(ofSize: 26), extraAttributes: [.kern: 26])
    static let type2 = LabelTheme(color: .yellow, font: UIFont.italicSystemFont(ofSize: 26))
    static let type3 = LabelTheme(color: .black, font: UIFont.systemFont(ofSize: 26))
    
    func set(to label: UILabel, with text: String = "") {
        if var attributes = extraAttributes {
            attributes[.font] = font
            attributes[.foregroundColor] = color
            label.attributedText = NSAttributedString(string: text, attributes: attributes)
        } else {
            label.textColor = color
            label.font = font
        }
    }
}

struct LabelContent {
    let text: String
    let theme: LabelTheme
    
    func set(to label: UILabel) {
        theme.set(to: label, with: text)
    }
}



Contents.swift


The class `Contents` has no reason to inherit `Themes`, if you make `LabelTheme` and `LabelContent` global as above.

And the class has no instance properties or states. Your `apply(content:to:)` method can be static.


import UIKit

class Contents {
    static let navContent = [
        LabelContent(text: "NAV IDENT", theme: .menuOption),
        LabelContent(text: "WPT LIST", theme: .menuOption),
        LabelContent(text: "FPL LIST", theme: .menuOption),
        LabelContent(text: "POS SENSORS", theme: .menuOption),
        LabelContent(text: "FIX INFO", theme: .menuOption),
        LabelContent(text: "DEPARTURE", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "DATALINK", theme: .menuOption),
        LabelContent(text: "FLT SUM", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "HOLD", theme: .menuOption),
        LabelContent(text: "ARRIVAL", theme: .menuOption),
        LabelContent(text: "POS INIT", theme: .menuOption),
        LabelContent(text: "DATA LOAD", theme: .menuOption),
        LabelContent(text: "PATTERNS", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "CONVERSION", theme: .menuOption),
        LabelContent(text: "MAINTENANCE", theme: .menuOption),
        LabelContent(text: "CROSS PTS", theme: .menuOption),
        
        ]
    
    
    public static func apply(contents: ArraySlice<LabelContent>, to labels: [UILabel]) {
        
        for index in 0..<labels.count {
            labels[index].text = ""
        }
        
        for (offset, content) in contents.enumerated() {
            labels[offset * 2 + 1].text = " "
            content.set(to: labels[offset * 2 + 2])
        }
    }
}

(I do not understand why you need to do such complex things in your `apply(content:to:)` method, so I kept the basic structure as-is. But it may be improved or may need some fix.)




ViewController.swift


import UIKit

class ViewController: UIViewController {
    
    @IBOutlet var uiTextLabels: [UILabel]!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    @IBAction func btnNav(_ sender: UIButton) {
        Contents.apply(contents: Contents.navContent[0..<12], to: uiTextLabels)
    }
    
} 


You may need some more fixes as I do not understand some parts of your code, but please try and see what you get.

Just add a " " after each char.


var example = "EXAMPLE"

for i in (1..<example.count).reversed() {

example.insert(" ", at: example.index(example.startIndex, offsetBy: i))

}

print(example)


or, without reversed()

var example2 = "EXAMPLE"

for i in 0..<example2.count-1 {

example2.insert(" ", at: example2.index(example2.startIndex, offsetBy: 2*i+1))

}

print(example2)

Just add a " " after each char.

I strongly recommend against that. What you want to do here is set the tracking. As far as I can tell iOS does not have a specific control for this, but you can simulate it using

NSKernAttributeName
, aka
NSAttributedString.Key.kern
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

You are right again, I misread the question, as a need to add a space (where it was asked for some space).


So the following works in an app


let style = [NSAttributedString.Key.kern: 4]

let attributeString = NSMutableAttributedString(string: "EXAMPLE", attributes: style)

label.attributedText = attributeString

How can I add the attribute you mentioned above to the following code:

I would like to have the menuOption with some spacing between letters.


class Themes: UITableViewCell {

    struct LabelTheme {
        let color: UIColor
        let font: UIFont
        
        static let menuOption = LabelTheme(color: .white, font: UIFont.systemFont(ofSize: 26))
        static let type2 = LabelTheme(color: .yellow, font: UIFont.italicSystemFont(ofSize: 26))
        static let type3 = LabelTheme(color: .black, font: UIFont.systemFont(ofSize: 26))
        
        func set(to label: UILabel) {
            label.textColor = color
            label.font = font
        }
    }
    
    struct LabelContent {
        let text: String
        let theme: LabelTheme
        
        func set(to label: UILabel) {
            label.text = text
            theme.set(to: label)
        }
    }

}

Do it in


        func set(to label: UILabel) {
             let style = [NSAttributedString.Key.kern: 4]
             let attributeString = NSMutableAttributedString(string: text, attributes: style)
             label.attributedText = attributeString
            // label.text = text
            theme.set(to: label)
        }

Whre should I do it?


Adding this code at the Themes.swift, I got the following error on line 38: Invalid redeclaration of 'set(to:)' and another one on line 39: Type 'NSAttributedString' has no member 'Key'

import UIKit

class Themes: UITableViewCell {

    struct LabelTheme {
        let color: UIColor
        let font: UIFont
        
        static let menuOption = LabelTheme(color: .white, font: UIFont.systemFont(ofSize: 26))
        static let type2 = LabelTheme(color: .yellow, font: UIFont.italicSystemFont(ofSize: 26))
        static let type3 = LabelTheme(color: .black, font: UIFont.systemFont(ofSize: 26))
        
        func set(to label: UILabel) {
            label.textColor = color
            label.font = font
        }
        
        
    }
    
    struct LabelContent {
        let text: String
        let theme: LabelTheme
        
        func set(to label: UILabel) {
            label.text = text
            theme.set(to: label)
        }
        
        func set(to label: UILabel) {
            let style = [NSAttributedString.Key.kern: 4]
            let attributeString = NSMutableAttributedString(string: text, attributes: style)
            label.attributedText = attributeString
            // label.text = text
            theme.set(to: label)
        }  
        
        
    }

}



Thank you Claude

The code was to replace the exiting one !


delete lines 25 to 28.


Which version of XCode and Swift are you using ?

That is for Swift 4.2


For earlier version, change to


     let style = [NSAttributedStringKey.kern: 4]
     let attributeString = NSMutableAttributedString(string: text, attributes: style)



That is an illustration that you have to be more precise in your questions.

Mentionning the XCode version, eventually Swift version and IOS version may be useful.

I'm using XCode Version 9.4.1 (9F2000)


And deleting the lines you mentioned, I still have the following error message: Type 'NSAttributedString' has no member 'Key'

OK, for 9.4.1, need to use


let style = [NSAttributedStringKey.kern: 4]

Thanks Claude, I'll try to be more precise in my question...


Now, there is no error, but It's doing nothing with my labels. In this example, all the items from navContent were supposed to be presented in .red colour, but it's white. And there is no error, anywhere... Check all my code:


Themes.Swift

import UIKit

class Themes: UITableViewCell {

    struct LabelTheme {
        let color: UIColor
        let font: UIFont
        
        static let menuOption = LabelTheme(color: .red, font: UIFont.systemFont(ofSize: 26))
        static let type2 = LabelTheme(color: .yellow, font: UIFont.italicSystemFont(ofSize: 26))
        static let type3 = LabelTheme(color: .black, font: UIFont.systemFont(ofSize: 26))
        
        func set(to label: UILabel) {
            label.textColor = color
            label.font = font
        }
        
        
    }
    
    struct LabelContent {
        let text: String
        let theme: LabelTheme
        
        func set(to label: UILabel) {
            let style = [NSAttributedStringKey.kern: 4]
            let attributeString = NSMutableAttributedString(string: text, attributes: style)
            label.attributedText = attributeString
            theme.set(to: label)
        }
        

        
    }

}


Contents.swift

import UIKit

class Contents: Themes {
    static let navContent = [
        LabelContent(text: "NAV IDENT", theme: .menuOption),
        LabelContent(text: "WPT LIST", theme: .menuOption),
        LabelContent(text: "FPL LIST", theme: .menuOption),
        LabelContent(text: "POS SENSORS", theme: .menuOption),
        LabelContent(text: "FIX INFO", theme: .menuOption),
        LabelContent(text: "DEPARTURE", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "DATALINK", theme: .menuOption),
        LabelContent(text: "FLT SUM", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "HOLD", theme: .menuOption),
        LabelContent(text: "ARRIVAL", theme: .menuOption),
        LabelContent(text: "POS INIT", theme: .menuOption),
        LabelContent(text: "DATA LOAD", theme: .menuOption),
        LabelContent(text: "PATTERNS", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "CONVERSION", theme: .menuOption),
        LabelContent(text: "MAINTENANCE", theme: .menuOption),
        LabelContent(text: "CROSS PTS", theme: .menuOption),
        
    ]
    
    
    public func apply(content: ArraySlice<LabelContent>, to labels: [UILabel]) {
        
        for index in 0..<labels.count {
            labels[index].text = ""
        }
        
        for (offset, content) in content.enumerated() {
            labels[offset * 2 + 1].text = " "
            labels[offset * 2 + 2].text = content.text
        }
    }
    
}


ViewController:

import UIKit

class ViewController: UIViewController {

    @IBOutlet var uiTextLabels: [UILabel]!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    @IBAction func btnNav(_ sender: UIButton) {
        Contents().apply(content: Contents.navContent[0..<12], to: uiTextLabels)
        
    }
    
}

Stop going to the wrong direction.


I'll give you a full answer for you, wait until then.

Accepted Answer

Themes.swift


First of all, you have no need to enclose two structs inside a class.

Second, if you want to apply extra spacing using `kern` (though I'm not sure if it is the best way for you) only for menuOption,

`LabelTheme` needs to be modified, just touching a very limited part of `LabelContent` is not sufficient.


import UIKit

struct LabelTheme {
    let color: UIColor
    let font: UIFont
    let extraAttributes: [NSAttributedStringKey: Any]?
    
    init(color: UIColor, font: UIFont, extraAttributes: [NSAttributedStringKey: Any]? = nil) {
        self.color = color
        self.font = font
        self.extraAttributes = extraAttributes
    }
    
    static let menuOption = LabelTheme(color: .red, font: UIFont.boldSystemFont(ofSize: 26), extraAttributes: [.kern: 26])
    static let type2 = LabelTheme(color: .yellow, font: UIFont.italicSystemFont(ofSize: 26))
    static let type3 = LabelTheme(color: .black, font: UIFont.systemFont(ofSize: 26))
    
    func set(to label: UILabel, with text: String = "") {
        if var attributes = extraAttributes {
            attributes[.font] = font
            attributes[.foregroundColor] = color
            label.attributedText = NSAttributedString(string: text, attributes: attributes)
        } else {
            label.textColor = color
            label.font = font
        }
    }
}

struct LabelContent {
    let text: String
    let theme: LabelTheme
    
    func set(to label: UILabel) {
        theme.set(to: label, with: text)
    }
}



Contents.swift


The class `Contents` has no reason to inherit `Themes`, if you make `LabelTheme` and `LabelContent` global as above.

And the class has no instance properties or states. Your `apply(content:to:)` method can be static.


import UIKit

class Contents {
    static let navContent = [
        LabelContent(text: "NAV IDENT", theme: .menuOption),
        LabelContent(text: "WPT LIST", theme: .menuOption),
        LabelContent(text: "FPL LIST", theme: .menuOption),
        LabelContent(text: "POS SENSORS", theme: .menuOption),
        LabelContent(text: "FIX INFO", theme: .menuOption),
        LabelContent(text: "DEPARTURE", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "DATALINK", theme: .menuOption),
        LabelContent(text: "FLT SUM", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "HOLD", theme: .menuOption),
        LabelContent(text: "ARRIVAL", theme: .menuOption),
        LabelContent(text: "POS INIT", theme: .menuOption),
        LabelContent(text: "DATA LOAD", theme: .menuOption),
        LabelContent(text: "PATTERNS", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: " ", theme: .menuOption),
        LabelContent(text: "CONVERSION", theme: .menuOption),
        LabelContent(text: "MAINTENANCE", theme: .menuOption),
        LabelContent(text: "CROSS PTS", theme: .menuOption),
        
        ]
    
    
    public static func apply(contents: ArraySlice<LabelContent>, to labels: [UILabel]) {
        
        for index in 0..<labels.count {
            labels[index].text = ""
        }
        
        for (offset, content) in contents.enumerated() {
            labels[offset * 2 + 1].text = " "
            content.set(to: labels[offset * 2 + 2])
        }
    }
}

(I do not understand why you need to do such complex things in your `apply(content:to:)` method, so I kept the basic structure as-is. But it may be improved or may need some fix.)




ViewController.swift


import UIKit

class ViewController: UIViewController {
    
    @IBOutlet var uiTextLabels: [UILabel]!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    @IBAction func btnNav(_ sender: UIButton) {
        Contents.apply(contents: Contents.navContent[0..<12], to: uiTextLabels)
    }
    
} 


You may need some more fixes as I do not understand some parts of your code, but please try and see what you get.

I have nothing to say, but Thank you!

It's perfect!

Change the space between Letters
 
 
Q