Bold between two special characters in a String SwiftUI 2.0 | NSRegularExpression

Viewed 76

Question:

  1. ( * 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 ?
  2. As you can see in the json output, I can use the \n character, but the \n character 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.

enter image description here

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])
}
0 Answers
Related