4

I want to change the text color of a specific text within a UITextView which matches an index of an array. I was able to slightly modify this answer but unfortunatly the text color of each matching phrase is only changed once.

var chordsArray = ["Cmaj", "Bbmaj7"]
func getColoredText(textView: UITextView) -> NSMutableAttributedString {
    let text = textView.text
    let string:NSMutableAttributedString = NSMutableAttributedString(string: text)
    let words:[String] = text.componentsSeparatedByString(" ")
    for word in words {
        if (chordsArray.contains(word)) {
            let range:NSRange = (string.string as NSString).rangeOfString(word)
            string.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: range)
        }
    }
    chords.attributedText = string
    return string
}

Outcome
outcome

Community
  • 1
  • 1
kye
  • 2,166
  • 3
  • 27
  • 41
  • `NSAttributedString` is your answer. Use colored attributes for the part you want to be colored and make it one string. Put rest in plain attributed string. Now combine the two attributedstrings. – NSNoob Nov 02 '15 at 07:03
  • @NSNoob don't want to bother you but could you please provide an example? I'm having a hard time understanding how to split the contained text and plain text into two different NSAttrubttedStrings. – kye Nov 02 '15 at 07:36
  • are you editing the textView? I can see a keyboard down there? So I assume you want to color the text whenever user enters those keywords defined in chords array? Just noticed the keyboard so I assume what you really want is what I just described? Recoloring right on data entry? – NSNoob Nov 02 '15 at 07:39
  • Yes I am and that's correct @NSNoob – kye Nov 02 '15 at 07:40
  • Yes I am, the function is being called on textdidchange – kye Nov 02 '15 at 07:43
  • Can you please make one change? Call the method `getColoredText` in `optional func textViewDidEndEditing(_ textView: UITextView)` . Also is chords name of your textView? – NSNoob Nov 02 '15 at 07:46
  • outcome remains the same and I made a slight mistake a apologize. I forgot to mention the function is called on `shouldChangeText` (changes color while typing) not on `textdidchange`). – kye Nov 02 '15 at 07:58
  • It should not be called there as shouldChangeText is called before editing happens so the text you get does not contain the data recently added by user. textViewDidEndEditing ought to work I am curious why it did not work. Did you set your VC as textViewDelegate and set delegate of the textView correctly? IOW, does it enter the delegate code block? – NSNoob Nov 02 '15 at 08:00
  • Make sure you have done `class MyViewController: UIViewController, UITextViewDelegate` in .swift file and in `viewdidLoad` method you should do `myTextView.delegate = self` – NSNoob Nov 02 '15 at 08:04
  • I've tired `textViewDidEndEditing`, only problem is detection is not real time, it only takes place after done button has been touched. I've made sure delegates are set (thought that was the issue at first..it wasnt) – kye Nov 02 '15 at 08:09
  • be careful what you ask for. Add this line to your `viewdidLoad` and let me know: `yourTextView.addTarget(self, action:"yourColorChangingMethodName", forControlEvents:.EditingChanged)` – NSNoob Nov 02 '15 at 08:13
  • i get this error `'UITextView' has no member 'addTarget'`. i tried using targetaction but im unable to set a forControlEvent – kye Nov 02 '15 at 08:41
  • Sorry for that I work in Obj-C not Swift so I make mistakes. So yeah follow whatever procedure it is to add target method to TextView and give this Control Event `UIControlEventEditingChanged`. I think you guys write it like `UIControlEvents.EditingChanged` – NSNoob Nov 02 '15 at 08:45

3 Answers3

5

In case, someone needs it in swift 4. This is what I get from my Xcode 9 playground :).

import UIKit
import PlaygroundSupport
class MyViewController : UIViewController
{
    override func loadView()
    {
        let view = UIView()
        view.backgroundColor = .white

        let textView = UITextView()
        textView.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
        textView.text = "@Kam @Jam @Tam @Ham"
        textView.textColor = .black
        view.addSubview(textView)
        self.view = view

        let query = "@"

        if let str = textView.text {
            let text = NSMutableAttributedString(string: str)
            var searchRange = str.startIndex..<str.endIndex
            while let range = str.range(of: query, options: NSString.CompareOptions.caseInsensitive, range: searchRange) {
                text.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.gray, range: NSRange(range, in: str))
                searchRange = range.upperBound..<searchRange.upperBound
            }
            textView.attributedText = text
        }
    }
}
PlaygroundPage.current.liveView = MyViewController()

I think for swift 3, you need to convert Range(String.Index) to NSRange manually like this.

 let start = str.distance(from: str.startIndex, to: range.lowerBound)
 let len = str.distance(from: range.lowerBound, to: range.upperBound)
 let nsrange = NSMakeRange(start, len)
 text.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.gray, range: nsrange)
4

Swift 4.2 and 5

let string = "* Your receipt photo was not clear or did not capture the entire receipt details. See our tips here.\n* Your receipt is not from an eligible grocery, convenience or club store."
let attributedString = NSMutableAttributedString.init(string: string)
let range = (string as NSString).range(of: "See our tips")
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: range)
txtView.attributedText = attributedString
txtView.isUserInteractionEnabled = true
txtView.isEditable = false

Output

enter image description here

Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
  • 1
    for people having issue with NSAttributedString.Key.foregroundColor part of the code, use NSForegroundColorAttributeName instead – ACAkgul May 15 '19 at 08:43
2

Sorry, I just noticed your message. Here is a working example (tested in a playground):

import UIKit


func apply (string: NSMutableAttributedString, word: String) -> NSMutableAttributedString {
    let range = (string.string as NSString).rangeOfString(word)
    return apply(string, word: word, range: range, last: range)
}

func apply (string: NSMutableAttributedString, word: String, range: NSRange, last: NSRange) -> NSMutableAttributedString {
    if range.location != NSNotFound {
        string.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: range)
        let start = last.location + last.length
        let end = string.string.characters.count - start
        let stringRange = NSRange(location: start, length: end)
        let newRange = (string.string as NSString).rangeOfString(word, options: [], range: stringRange)
        apply(string, word: word, range: newRange, last: range)
    }
    return string
}

var chordsArray = ["Cmaj", "Bbmaj7"]
var text = "Cmaj Bbmaj7 I Love Swift Cmaj Bbmaj7 Swift"
var newText = NSMutableAttributedString(string: text)

for word in chordsArray {
    newText = apply(newText, word: word)
}

newText
chapani
  • 420
  • 4
  • 14