Make part of a UILabel bold in Swift
Asked Answered
C

9

64

I have a UILabel I've made programmatically as:

var label = UILabel()

I've then declared some styling for the label, including a font, such as:

label.frame = CGRect(x: 20, y: myHeaderView.frame.height / 2, width: 300, height: 30)
label.font = UIFont(name: "Typo GeoSlab Regular Demo", size: 15)
label.textColor = UIColor(hue: 0/360, saturation: 0/100, brightness: 91/100, alpha: 1)

The first part of the label will always read : "Filter:" then followed by another part of the string, for example, "Most popular"

I would like the word filter to be in bold, so the whole thing would look like:

Filter: Most popular

I want to simplest way of creating this effect. I've been searching the internet for how to achieve this and there are so many ways, some which just look like pages of code. And most of it seems to be in Objective-C. I would like it in Swift please :)

I don't know if i'm on the right lines, but is this what NSRange can help achieve?

Update

I use a series of if statements to change my label variable. Such as:

if indexArray == 1 {

    label.text = "Filter: Film name"
            
} else if indexArray == 2 {
            
    label.text = "Filter: Most popular"
            
} else if indexArray == 3 {
            
    label.text = "Filter: Star rating"
            
}
Corroboration answered 7/4, 2016 at 20:39 Comment(0)
W
96

You will want to use attributedString which allows you to style parts of a string etc. This can be done like this by having two styles, one normal, one bold, and then attaching them together:

let boldText = "Filter:"
let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]
let attributedString = NSMutableAttributedString(string:boldText, attributes:attrs)

let normalText = "Hi am normal"
let normalString = NSMutableAttributedString(string:normalText)

attributedString.append(normalString)

When you want to assign it to a label:

label.attributedText = attributedString
Writing answered 7/4, 2016 at 20:50 Comment(4)
Thanks for your answer! It does seem to work. I have an amendment needed though. How would I go about it, if I needed normalText to be a different string depending on an index count. See the Update section of my question. Thanks in advance!Corroboration
You can pull normalText out of an array and just get it by an index : normalText = normalTextArray[index] where this is like let normalTextArray = ["Hello", "Two", "Three"]Writing
Value of type 'String' has no member 'appendAttributedString'Relate
@Relate With newer versions of Swift, use attributedString.append(normalString).Seldon
T
94

You can use NSMutableAttributedString and NSAttributedString to create customized string. The function below makes given boldString bold in given string.

Swift 3

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                     attributes: [NSFontAttributeName: font])
    let boldFontAttribute: [String: Any] = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}

Example usage

authorLabel.attributedText = attributedText(withString: String(format: "Author : %@", user.name), boldString: "Author", font: authorLabel.font)

Swift 4

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                     attributes: [NSAttributedStringKey.font: font])
    let boldFontAttribute: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}

Swift 4.2 and 5

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                 attributes: [NSAttributedString.Key.font: font])
    let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}
Testee answered 19/9, 2017 at 10:12 Comment(2)
Works beautifully, this should be the accepted answer.Relate
make range search case insensitive also may be helpful let range = (self as NSString).range(of: boldString, options: CompareOptions.caseInsensitive)Tenderhearted
D
37

Result:

enter image description here

Swift 4.2 & 5.0:

First off we create a protocol that UILabel, UITextField and UITextView can adopt.

public protocol ChangableFont: AnyObject {
    var rangedAttributes: [RangedAttributes] { get }
    func getText() -> String?
    func set(text: String?)
    func getAttributedText() -> NSAttributedString?
    func set(attributedText: NSAttributedString?)
    func getFont() -> UIFont?
    func changeFont(ofText text: String, with font: UIFont)
    func changeFont(inRange range: NSRange, with font: UIFont)
    func changeTextColor(ofText text: String, with color: UIColor)
    func changeTextColor(inRange range: NSRange, with color: UIColor)
    func resetFontChanges()
}

We want to be able to add multiple changes to our text, therefore we create the rangedAttributes property. It's a custom struct that holds attributes and the range in which they are applied.

public struct RangedAttributes {

    public let attributes: [NSAttributedString.Key: Any]
    public let range: NSRange

    public init(_ attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
        self.attributes = attributes
        self.range = range
    }
}

Another problem is that UILabel its font property is strong and UITextField its font property is weak/optional. To make them both work with our ChangableFont protocol we include the getFont() -> UIFont? method. This also counts for UITextView its text and attributedText properties. That's why we implement the getter and setter methods for them as well.

extension UILabel: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

    public func getFont() -> UIFont? {
        return font
    }
}

extension UITextField: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

    public func getFont() -> UIFont? {
        return font
    }
}

extension UITextView: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

    public func getFont() -> UIFont? {
        return font
    }
}

Now we can go ahead and create the default implementation for UILabel, UITextField and UITextView by extending our protocol.

public extension ChangableFont {

    var rangedAttributes: [RangedAttributes] {
        guard let attributedText = getAttributedText() else {
            return []
        }
        var rangedAttributes: [RangedAttributes] = []
        let fullRange = NSRange(
            location: 0,
            length: attributedText.string.count
        )
        attributedText.enumerateAttributes(
            in: fullRange,
            options: []
        ) { (attributes, range, stop) in
            guard range != fullRange, !attributes.isEmpty else { return }
            rangedAttributes.append(RangedAttributes(attributes, inRange: range))
        }
        return rangedAttributes
    }

    func changeFont(ofText text: String, with font: UIFont) {
        guard let range = (self.getAttributedText()?.string ?? self.getText())?.range(ofText: text) else { return }
        changeFont(inRange: range, with: font)
    }

    func changeFont(inRange range: NSRange, with font: UIFont) {
        add(attributes: [.font: font], inRange: range)
    }

    func changeTextColor(ofText text: String, with color: UIColor) {
        guard let range = (self.getAttributedText()?.string ?? self.getText())?.range(ofText: text) else { return }
        changeTextColor(inRange: range, with: color)
    }

    func changeTextColor(inRange range: NSRange, with color: UIColor) {
        add(attributes: [.foregroundColor: color], inRange: range)
    }

    private func add(attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
        guard !attributes.isEmpty else { return }

        var rangedAttributes: [RangedAttributes] = self.rangedAttributes

        var attributedString: NSMutableAttributedString

        if let attributedText = getAttributedText() {
            attributedString = NSMutableAttributedString(attributedString: attributedText)
        } else if let text = getText() {
            attributedString = NSMutableAttributedString(string: text)
        } else {
            return
        }

        rangedAttributes.append(RangedAttributes(attributes, inRange: range))

        rangedAttributes.forEach { (rangedAttributes) in
            attributedString.addAttributes(
                rangedAttributes.attributes,
                range: rangedAttributes.range
            )
        }

        set(attributedText: attributedString)
    }

    func resetFontChanges() {
        guard let text = getText() else { return }
        set(attributedText: NSMutableAttributedString(string: text))
    }
}

With in the default implementation I use a little helper method for getting the NSRange of a substring.

public extension String {

    func range(ofText text: String) -> NSRange {
        let fullText = self
        let range = (fullText as NSString).range(of: text)
        return range
    }
}

We're done! You can now change parts of the text its font and text color.

titleLabel.text = "Welcome"
titleLabel.font = UIFont.systemFont(ofSize: 70, weight: .bold)
titleLabel.textColor = UIColor.black
titleLabel.changeFont(ofText: "lc", with: UIFont.systemFont(ofSize: 60, weight: .light))
titleLabel.changeTextColor(ofText: "el", with: UIColor.blue)
titleLabel.changeTextColor(ofText: "co", with: UIColor.red)
titleLabel.changeTextColor(ofText: "m", with: UIColor.green)
Dowland answered 20/7, 2018 at 13:13 Comment(11)
Thanx for this solution! You could publish this as a CocoaPods library.Calfee
Thanks, I’m glad I could help you out. Haha great suggestion.Dowland
Wow, perfect. ThanksScend
@LloydKeijzer what to do for UITextView?Bosporus
@pradipsutariya If you conform UITextView to ChangableFont it should work. If it doesn't let me know and I will write an update to this answerDowland
@LloydKeijzer I tried but didn't get success. it would be great if you update code for UITextView. Thanks for your consideration in advance.Bosporus
@pradipsutariya I've updated the answer to support UITextView as well.Dowland
@LloydKeijzer Its working perfect for UITextView, Thank you very much for your time.Bosporus
@LloydKeijzer Would it be possible to set a link attribute for UITextView? i.e change link text color, font, etc.Bosporus
@pradipsutariya I'm glad it's working. Regarding your question it should be possible by extracting the ranges (NSRange) for all links in your text. After extracting you could loop over the collection of ranges and call func changeFont(inRange range: NSRange, with font: UIFont) and func changeTextColor(inRange range: NSRange, with color: UIColor).Dowland
to change background colour of part of text use this func changeBackgroundColor(inRange range: NSRange, with color: UIColor) { add(attributes: [.backgroundColor: color], inRange: range) } and func changeBackGroundColor(ofText text: String, with color: UIColor) { guard let range = (self.getAttributedText()?.string ?? self.getText())?.range(ofText: text) else { return } changeBackGroundColor(inRange: range, with: color) }Charbonnier
A
9

Swift 4 alternative:

let attrs = [NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 14)]
let attributedString = NSMutableAttributedString(string: "BOLD TEXT", attributes:attrs)
let normalString = NSMutableAttributedString(string: "normal text")
attributedString.append(normalString)
myLabel.attributedText = attributedString
Alcoran answered 20/1, 2018 at 6:21 Comment(0)
T
7

You can directly do on String if you prefer:

extension String {
func withBoldText(text: String, font: UIFont? = nil) -> NSAttributedString {
  let _font = font ?? UIFont.systemFont(ofSize: 14, weight: .regular)
  let fullString = NSMutableAttributedString(string: self, attributes: [NSAttributedString.Key.font: _font])
  let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: _font.pointSize)]
  let range = (self as NSString).range(of: text)
  fullString.addAttributes(boldFontAttribute, range: range)
  return fullString
}}

Usage:

label.attributeString = "my full string".withBoldText(text: "full")
Tancred answered 1/3, 2019 at 15:41 Comment(2)
Had to change to attributedText to work. titleLabel.attributedText = "my full string".withBoldText(text: "full")Anticline
@StefanoPedroli How can I used it with custom font?Skillful
A
4

for the ones who prefer extensions

Swift 5.0

    /// will set a regual and a bold text in the same label
    public func setRegualAndBoldText(regualText: String,
                                       boldiText: String) {

        let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: font.pointSize)]
        let regularString = NSMutableAttributedString(string: regualText)
        let boldiString = NSMutableAttributedString(string: boldiText, attributes:attrs)
        regularString.append(boldiString)
        attributedText = regularString
    }

and use:

label.setRegualAndBoldText(regualText: "height: ", boldiText: "1.65 :(")
Apatetic answered 3/6, 2019 at 11:44 Comment(0)
E
2

Just sharing my own quite-flexible implementation in Swift 4.0. Cause there are some requirements, like mine currently, that you need to set not only bold but italic the part of a label's text.

import UIKit

extension UILabel {

    /** Sets up the label with two different kinds of attributes in its attributed text.
     *  @params:
     *  - primaryString: the normal attributed string.
     *  - secondaryString: the bold or highlighted string.
     */

    func setAttributedText(primaryString: String, textColor: UIColor, font: UIFont, secondaryString: String, secondaryTextColor: UIColor, secondaryFont: UIFont) {

        let completeString = "\(primaryString) \(secondaryString)"

        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .center

        let completeAttributedString = NSMutableAttributedString(
            string: completeString, attributes: [
                .font: font,
                .foregroundColor: textColor,
                .paragraphStyle: paragraphStyle
            ]
        )

        let secondStringAttribute: [NSAttributedStringKey: Any] = [
            .font: secondaryFont,
            .foregroundColor: secondaryTextColor,
            .paragraphStyle: paragraphStyle
        ]

        let range = (completeString as NSString).range(of: secondaryString)

        completeAttributedString.addAttributes(secondStringAttribute, range: range)

        self.attributedText = completeAttributedString
    }
}
Elouise answered 21/3, 2018 at 11:21 Comment(0)
K
2

enter image description here

If you know which character place values you want to bold I created a function which takes ranges of characters and optional fonts (use nil if you just want to use the standard system font of size 12), and returns an NSAttributedString which you can attach to a label as its attributed text. I wanted to bolden the 0th, 10th, 22-23rd, 30th and 34th characters of my string so i used [[0,0], [10,10], [22,23], [30,30], [34,34]] for my boldCharactersRanges value.

Usage:

func boldenParts(string: String, boldCharactersRanges: [[Int]], regularFont: UIFont?, boldFont: UIFont?) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.font: regularFont ?? UIFont.systemFont(ofSize: 12)])
    let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: boldFont ?? UIFont.boldSystemFont(ofSize: regularFont?.pointSize ?? UIFont.systemFontSize)]
    for range in boldCharactersRanges {
        let currentRange = NSRange(location: range[0], length: range[1]-range[0]+1)
        attributedString.addAttributes(boldFontAttribute, range: currentRange)
        }
    return attributedString
}

override func viewDidLoad() {
    super.viewDidLoad()
    let label = UILabel()
    label.frame = CGRect(x: 0, y: 0, width: 180, height: 50)
    label.numberOfLines = 0
    label.center = view.center
    let text = "Under the pillow is a vogue article"
    let secretMessage = boldenParts(string: text, boldCharactersRanges: [[0,0], [10,10], [22,23], [30,30], [34,34]], regularFont: UIFont(name: "Avenir", size: 15), boldFont: UIFont(name: "Avenir-Black", size: 15))
    label.attributedText = secretMessage
    view.addSubview(label)
}
Kim answered 7/9, 2020 at 4:35 Comment(2)
How can I add Color in your boldenParts functionSkillful
The boldFontAttribute is a dictionary so you can add a comma to the end of NSAttributedString.Key.font: boldFont ?? UIFont.boldSystemFont(ofSize: regularFont?.pointSize ?? UIFont.systemFontSize) and then make a new key value pair of NSAttributedString.Key.foregroundColor : YourUIColorKim
S
1

Swift 4.0 solution

let font = UIFont.systemFont(ofSize: 14)

func boldSearchResult(searchString: String, resultString: String) -> NSMutableAttributedString {
    let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: resultString)

    guard let regex  = try? NSRegularExpression(pattern: searchString.lowercased(), options: []) else {
        return attributedString
    }

    let range: NSRange = NSMakeRange(0, resultString.count)

    regex.enumerateMatches(in: resultString.lowercased(), options: [], range: range) { (textCheckingResult, matchingFlags, stop) in
        guard let subRange = textCheckingResult?.range else {
            return
        }

        attributedString.addAttributes([NSAttributedString.Key.font : font], range: subRange)
    }

    return attributedString
}
Stray answered 23/11, 2018 at 16:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.