'Making parts of text bold in SwiftUI

I was wondering how I would make only sections of a text bold while keep the rest 'regular' in SwiftUI.

I currently have:

Text("Coronavirus Disease of 2019")

and I want it to print out COronaVirus Disease of 2019 and haven't been able to get only some parts bold.



Solution 1:[1]

iOS 15+ (Swift 5.5 +)

SwiftUI has built-in support for rendering Markdown


Add double asterisks (**) arroud the characters that you want to make bold.

Text("**CO**rona**V**irus **D**isease of 20**19**")

Add underscore arround the charachters that you want to make italic.

Text("Is this text _emphasized_?")

String variable

Use init(_ value: String)

Creates a localized string key from the given string value.

let bold = "This text is **bold**"
Text(.init(bold))

Attributed text

Use init(_ attributedContent: AttributedString)

Creates a text view that displays styled attributed content.

let markdownText = try! AttributedString(markdown: "This text is **bold**")
Text(markdownText)

See also:

init(_ attributedContent: AttributedString) - https://developer.apple.com

Solution 2:[2]

If you don't need to translate it here is possible fast variant

demo

Text("CO").bold() + Text("rona") + Text("VI").bold() + 
    Text("rus Disease of 20") + Text("19").bold()

alternate is to use NSAttributedString with UIViewRepresentable of UILabel.

Solution 3:[3]

A quick note just to add onto Asperi's great answer, if you need to apply frame or padding modifiers to your text you'll need to group the text first and then add your modifiers to the group.

Group { Text("CO").bold() + Text("rona") + Text("VI").bold() + Text("rus Disease of 20") + Text("19").bold() }.frame(width: 100, height: 100).padding(.horizontal)

Solution 4:[4]

Flavours of this question crop up a lot and for a newcomer to Swift without a background in Objective-C, the solutions emerge grudgingly. Several of the above answers are excellent, but to summarize perhaps the optimal solution to the question as asked,

Group {
    Text("CO").bold() +
    Text("rona") +
    Text("V").bold() +
    Text("irus ") +
    Text("D").bold() +
    Text("isease of 20") +
    Text("19").bold()
}
.font(.caption)
.frame(width: 300)

(Group{} was the secret sauce for me)

Solution 5:[5]

Swift 5, iOS 13

This article is about changing the color of text of characters, but you could equally apply the technique it is using [a bit mask] to make some characters bold, flash, animate whatever?

https://medium.com/@marklucking/an-interesting-challenge-with-swiftui-9ebb26e77376

The two core parts you need to focus on are ..

ForEach((0 ..< letter.count), id: \.self) { column in
          Text(letter[column])
            .foregroundColor(colorCode(gate: Int(self.gate), no: column) ? Color.black: Color.red)
            .font(Fonts.futuraCondensedMedium(size: fontSize))

        }

And this one to mask the text...

func colorCode(gate:Int, no:Int) -> Bool {

  let bgr = String(gate, radix:2).pad(with: "0", toLength: 16)
  let bcr = String(no, radix:2).pad(with: "0", toLength: 16)
  let binaryColumn = 1 << no - 1

  let value = UInt64(gate) & UInt64(binaryColumn)
  let vr = String(value, radix:2).pad(with: "0", toLength: 16)

  print("bg ",bgr," bc ",bcr,vr)
  return value > 0 ? true:false
}

Solution 6:[6]

The solution proposed by @mahan is great but it has a limitation that it works fine on iOS 15 but not on iOS 14.

So I think this is a better solution for those who need to support iOS 14, the solution was copied from this website: https://www.avanderlee.com/swiftui/text-weight-combinations/

Final code it look like this:

    @main
struct RichTextApp: App {
    var body: some Scene {
        WindowGroup {
            RichText("SwiftLee - A *weekly blog* about Swift, iOS and Xcode *Tips and Tricks*")
                .padding()
                .multilineTextAlignment(.center)
        }
    }
}

(you can customize fonts and have vars in the text, example:)

RichText(" ... *\(viewModel.title)* ...")

enter image description here

And the code is:

import SwiftUI

struct RichText: View {

    struct Element: Identifiable {
        let id = UUID()
        let content: String
        let isBold: Bool

        init(content: String, isBold: Bool) {
            var content = content.trimmingCharacters(in: .whitespacesAndNewlines)

            if isBold {
                content = content.replacingOccurrences(of: "*", with: "")
            }

            self.content = content
            self.isBold = isBold
        }
    }

    let elements: [Element]

    init(_ content: String) {
        elements = content.parseRichTextElements()
    }

    var body: some View {
        var content = text(for: elements.first!)
        elements.dropFirst().forEach { (element) in
            content = content + self.text(for: element)
        }
        return content
    }
    
    private func text(for element: Element) -> Text {
        let postfix = shouldAddSpace(for: element) ? " " : ""
        if element.isBold {
            return Text(element.content + postfix)
                .fontWeight(.bold)
        } else {
            return Text(element.content + postfix)
        }
    }

    private func shouldAddSpace(for element: Element) -> Bool {
        return element.id != elements.last?.id
    }
    
}



extension String {

    /// Parses the input text and returns a collection of rich text elements.
    /// Currently supports asterisks only. E.g. "Save *everything* that *inspires* your ideas".
    ///
    /// - Returns: A collection of rich text elements.
    func parseRichTextElements() -> [RichText.Element] {
        let regex = try! NSRegularExpression(pattern: "\\*{1}(.*?)\\*{1}")
        let range = NSRange(location: 0, length: count)

        /// Find all the ranges that match the regex *CONTENT*.
        let matches: [NSTextCheckingResult] = regex.matches(in: self, options: [], range: range)
        let matchingRanges = matches.compactMap { Range<Int>($0.range) }

        var elements: [RichText.Element] = []

        // Add the first range which might be the complete content if no match was found.
        // This is the range up until the lowerbound of the first match.
        let firstRange = 0..<(matchingRanges.count == 0 ? count : matchingRanges[0].lowerBound)

        self[firstRange].components(separatedBy: " ").forEach { (word) in
            guard !word.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
            elements.append(RichText.Element(content: String(word), isBold: false))
        }

        // Create elements for the remaining words and ranges.
        for (index, matchingRange) in matchingRanges.enumerated() {
            let isLast = matchingRange == matchingRanges.last

            // Add an element for the matching range which should be bold.
            let matchContent = self[matchingRange]
            elements.append(RichText.Element(content: matchContent, isBold: true))

            // Add an element for the text in-between the current match and the next match.
            let endLocation = isLast ? count : matchingRanges[index + 1].lowerBound
            let range = matchingRange.upperBound..<endLocation
            self[range].components(separatedBy: " ").forEach { (word) in
                guard !word.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
                elements.append(RichText.Element(content: String(word), isBold: false))
            }
        }

        return elements
    }

    /// - Returns: A string subscript based on the given range.
    subscript(range: Range<Int>) -> String {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        let endIndex = index(self.startIndex, offsetBy: range.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Asperi
Solution 3 LJ White
Solution 4 Flair
Solution 5 user3069232
Solution 6 Tiago Mendes