SwiftUI: Combining Text on the same line

Viewed 897

the SwiftUI App I'm writing has large blocks of text, so I'm trying to create a "markup language" I can use in my JSON files that hold the text, to define when a word should be bolded. The issue is, I can't seem to stop new text blocks going to a different line.

Here is the relevant code as I currently have it.

struct formattedText: View {
var text: String

var body: some View {
    let split = text.components(separatedBy: "**")
    Group {
        ForEach(split, id: \.self) { line in
            if line.hasPrefix("$$") {
                Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
                    .bold()
            } else {
                Text(line)
            }
        }
    }
    .lineLimit(nil)
    .multilineTextAlignment(.leading)
}

Using this code, I can put a **$$ before a word and ** after, to define it as bold.

Only issue is every time I bold a word it goes to a new line. I know the traditional way to fix this is:

Text("Simple ") + Text("Swift ") + Text("Guide")

This does not work with my ForEach loop though. Any suggestions?

1 Answers

ForEach creates separate Views. You really just want one Text, so you mean a for...in loop:

var body: some View {
    let split = text.components(separatedBy: "**")
    var result = Text("")
    for line in split {
        if line.hasPrefix("$$") {
            result = result + Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
                .bold()
        } else {
            result = result + Text(line)
        }
    }

    return result
        .lineLimit(nil)
        .multilineTextAlignment(.leading)
}

Since you may want a lot more things than just bold, you might find it useful to extract that part into its own function or collection of functions:

private func applyAttributes(line: String) -> Text {
    if line.hasPrefix("$$") {
        return Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
            .bold()
    } else {
        return Text(line)
    }
}

With that, constructing this is simpler:

var body: some View {
    text.components(separatedBy: "**")
        .map(applyAttributes)
        .reduce(Text(""), +)
        .lineLimit(nil)
        .multilineTextAlignment(.leading)
}
Related