Search code examples
swiftuilabeluicolor

How I can change text color for every 5 first words in label?


I get different text from API and I want change text color for every 5 first word. I try use range and attributes string, but I do something wrong and this not good work for me. How can i do it?

this is my code:

private func setMessageText(text: String) {
    let components = text.components(separatedBy: .whitespacesAndNewlines)
    let words = components.filter { !$0.isEmpty }

    if words.count >= 5 {
        let attribute = NSMutableAttributedString.init(string: text)

        var index = 0
        for word in words where index < 5 {

            let range = (text as NSString).range(of: word, options: .caseInsensitive)
            attribute.addAttribute(NSAttributedString.Key.foregroundColor, value: Colors.TitleColor, range: range)
            attribute.addAttribute(NSAttributedString.Key.font, value: Fonts.robotoBold14, range: range)
            index += 1
        }
        label.attributedText = attribute
    } else {
        label.text = text
    }
}

enter image description here


Solution

  • It's more efficient to get the index of the end of the 5th word and add color and font once for the entire range.

    And you are strongly discouraged from bridging String to NSString to get a subrange from a string. Don't do that. Use native Swift Range<String.Index>, there is a convenience API to convert Range<String.Index> to NSRange reliably.

    private func setMessageText(text: String) {
        let components = text.components(separatedBy: .whitespacesAndNewlines)
        let words = components.filter { !$0.isEmpty }
    
        if words.count >= 5 {
            let endOf5thWordIndex = text.range(of: words[4])!.upperBound
            let nsRange = NSRange(text.startIndex..<endOf5thWordIndex, in: text)
    
            let attributedString = NSMutableAttributedString(string: text)
            attributedString.addAttributes([.foregroundColor : Colors.TitleColor, .font : Fonts.robotoBold14], range: nsRange)
            label.attributedText = attributedString
        } else {
            label.text = text
        }
    }
    

    An alternative – more sophisticated – way is to use the dedicated API enumerateSubstrings(in:options: with option byWords

    func setMessageText(text: String) {
    
        var wordIndex = 0
        var attributedString : NSMutableAttributedString?
        text.enumerateSubstrings(in: text.startIndex..., options: .byWords) { (substring, substringRange, enclosingRange, stop) in
            if wordIndex == 4 {
                let endIndex = substringRange.upperBound
                let nsRange = NSRange(text.startIndex..<endIndex, in: text)
                attributedString = NSMutableAttributedString(string: text)
                attributedString!.addAttributes([.foregroundColor : Colors.TitleColor, .font : Fonts.robotoBold14], range: nsRange)
                stop = true
            }
            wordIndex += 1
        }
    
        if let attributedText = attributedString {
           label.attributedText = attributedText
        } else {
           label.text = text
        }
    }