Question:
- ( * test * ) The place where these two characters are is made bold, but after the bold operation is completed, a space is formed between these two characters. Why ?
- As you can see in the json output, I can use the
\ncharacter, but the\ncharacter does not work. Why ?
If you examine the image I added and my JSON data, there should be no spaces in the places I have shown with a red line in the image.
JSON:
"singular": ["*der* Mann","*des* Mann*es*\n*des* Man*s*","*dem* Mann\n*dem* Mann*e*","*den* Mann"]
Model:
struct TextGroup: 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
}
}
String Extension:
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() -> [TextGroup] {
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: [TextGroup] = []
// 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(TextGroup(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(TextGroup(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(TextGroup(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])
}
}
RichText:
struct RichText: View {
let elements: [TextGroup]
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: TextGroup) -> 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: TextGroup) -> Bool {
return element.id != elements.last?.id
}
}
Using:
ForEach(singular.indices, id: \.self) { index in
RichText(singular[index])
}
