31

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.

HangarRash
  • 7,314
  • 5
  • 5
  • 32
Thomas Braun
  • 1,109
  • 2
  • 13
  • 27

8 Answers8

58

iOS 15+ (Swift 5.5 +)

SwiftUI has built-in support for rendering Markdown.

It is GitHub flavored markdown. AttributedString converts both inline and block styles. SwiftUI renders inline styles (but not images at this time). We use the fantastic cmark-gfm library to parse the markdown string. - SwiftUI Frameworks Engineer - developer.apple.com

See more:

What is Markdown?


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

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

Use underscore (_) arround the charachters 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))

String interpolation

Use init(_ value: String)

Creates a localized string key from the given string value.

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

Attributed String

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

mahan
  • 12,366
  • 5
  • 48
  • 83
  • Actually, both `**Bold**` and `__Bold__` produces bold (and `*Italic*` and `_Italic_` renders italic) – JKvr Mar 03 '22 at 09:54
  • it should be added that if the string is broken into pieces due to size ex: Text("**Hello** there " + "world") the text will be rendered without the bold – barryalan2633 Apr 03 '22 at 20:58
  • 2
    @barryalan2633 updated my answer. You need to use Text(.init("**Hello** there " + "world")) – mahan Apr 04 '22 at 12:53
27

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
14

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)
LJ White
  • 615
  • 6
  • 6
6

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)

Flair
  • 2,609
  • 1
  • 29
  • 41
jws
  • 61
  • 1
  • 3
1

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
}
user3069232
  • 8,587
  • 7
  • 46
  • 87
1

iOS 14+

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])
    }
}
Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35
  • This is good but seems to remove new line from the text? – dqualias Feb 24 '22 at 08:50
  • @dqualias the \n is not working for you? example "*\(title)* \n \(content)" – Tiago Mendes Feb 24 '22 at 12:31
  • Thats right if i have a string like "*title* \n content" the newline is removed. I think the regex is removing the newline? – dqualias Feb 25 '22 at 08:11
  • The line "var content = content.trimmingCharacters(in: .whitespacesAndNewlines)" removes the newline. So maybe change to "var content = content.trimmingCharacters(in: .whitespaces)". – Claytog Aug 28 '23 at 05:48
0

Not only Bold...

You can apply any attribute (bold, color, font, etc) to any part of the string by using AttributedString:

iOS 15

Text now supports markdown and also you can create custom attributes:

enter image description here

You can even get defined attributes remotely like:

enter image description here

A fully supported fallback for all iOS versions!

Since it doesn't support directly on Text (till iOS 15), you can bring the UILabel there and modify it in anyway you like:

Implementation:
struct UIKLabel: UIViewRepresentable {

    typealias TheUIView = UILabel
    fileprivate var configuration = { (view: TheUIView) in }

    func makeUIView(context: UIViewRepresentableContext<Self>) -> TheUIView { TheUIView() }
    func updateUIView(_ uiView: TheUIView, context: UIViewRepresentableContext<Self>) {
        configuration(uiView)
    }
}
Usage:
var body: some View {
    UIKLabel {
        $0.attributedText = NSAttributedString(string: "HelloWorld")
    }
}
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
0

With the use of AttributedString you can use the following extension.


extension View {
    func attributedBold(text: String, boldText: String,
                        font: Font = .largeTitle) -> AttributedString
    {
        var result = AttributedString(text)
        result.font = font
        if let range = result.range(of: boldText) {
            result[range].font = font.bold()
        }
        result.foregroundColor = .white
        return result
    }
}

simply call

 Text(attributedBold(text: "Make me bold with attributed string",
                                        boldText: "bold",
                                        font: .title2))
dip
  • 3,548
  • 3
  • 24
  • 36