Get color changed words of attributed string - ios

I have a UITextView that allows to select words of the text by tapping it. If it is tapped the word is highlighted in color by changing the NSForegroundColor attribute.
Tapping it again deselects it by changing the color back to the textcolor.
Now I would need to know all selected words in the UITextView.
The first idea was to remove everything that is a special character and split the text at space. Then check if the color attribute is equals to the selected/highlighted color of each seperate word.
But attributed string doesn't allow to split at a character or remove components. Neither does NSAttributedString.
Second idea was to save the ranges of the highlighted parts in an array and iterate over it to get the highlighted parts. But this seems a bit too complicated for me, especially as I need the correct order of the words as they appear which is not guaranteed with an array, add/remove on each tap
(For example let's say the text is: "This is a test"
Tap this -> index 0
Tap test -> index 1
Tap this -> test becomes index 0
Tap this -> this becomes index 1
then the order is not good anymore.
I already figured out how to get the color of an attributed string. That's not the problem.
How can I iterate over the attributed string and figure out the words that have changed color or what is the best way to solve this problem?
Thank you!
Greetings

You can iterate over the attributed string looking for the color attribute.
The follow code demonstrates how:
// This generates a test attributed string.
// You actually want the attributedText property of your text view
let str = NSMutableAttributedString(string: "This is a test of the following code")
str.addAttributes([NSForegroundColorAttributeName:UIColor.red], range: NSMakeRange(0, 4))
str.addAttributes([NSForegroundColorAttributeName:UIColor.red], range: NSMakeRange(8, 1))
str.addAttributes([NSForegroundColorAttributeName:UIColor.red], range: NSMakeRange(15, 2))
print(str)
The above prints:
This{
NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
} is {
}a{
NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
} test {
}of{
NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
} the following code{
}
This code processes the attributed string. Any range of text formatted with a foreground color will be put into the words array.
var words = [String]()
str.enumerateAttribute(NSForegroundColorAttributeName, in: NSMakeRange(0, str.length), options: []) { (value, range, stop) in
if value != nil {
let word = str.attributedSubstring(from: range).string
words.append(word)
}
}
print(words)
This prints:
["This", "a", "of"]

I can suggest you to create some kind of storage for selected ranges, then based on this range you can customize look of this words, not the other way. It will allow you to access selected words every time without checking attributes of whole text.

While I agree with Piotr that you should store the Ranges, to answer your question:
attributedString.enumerateAttributes(in: NSMakeRange(0, attributedString.length), options: []) { attributes, range, _ in
if let color = attributes[NSForegroundColorAttributeName] as? UIColor,
color == YOUR_HIGHLIGHT_COLOR {
let nString = attributedString.string as NSString
let word = nString.substring(with: range)
// Do what you want with the word
}
}

Related

Swift 3 : How do you wrap content in a tableviewcell with multiple labels?

I've been trying to figure this out for a while. I have set constraints for each label with background color set. I set each label's line break to word wrap, but that still doesn't work. What I'm looking for is a label wrap like word wrap whether or not that exists. Thanks.
Here is a slightly different approach. You can customize the appearance of each tagged word with an attributed string. This has some limitations but depending on your requirements it could be a good fit for you. The below code is an example pointing you in the correct direction, however you still might need to write additional code for correctly wrapping the spaces or recognizing touch events.
let tags = ["Outdoors", "Working", "Learning"].map { " \($0) " }
let text = tags.joined(separator: " ")
let ranges = tags.compactMap { text.range(of: $0) }
let attributedText = NSMutableAttributedString(string: text)
for range in ranges {
attributedText.addAttributes([.backgroundColor: UIColor.green], range: NSRange(range, in: text))
}
textView.attributedText = attributedText

Swift how to add background color just around text in a label?

How do you add a background color around each variable inside the interests variable? Just around text not the spaces.
var interests = "\(int01) \(int02) \(int03) \(int04) \(int05) \(int06)"
I want it to look like this:
You can use a regex to find anything but white spaces, use a while loop to find its occurrences in a string and use those ranges to change the background color of an attributed string:
Swift 4
let mutable = NSMutableAttributedString(string: interests)
var startIndex = interests.startIndex
while let range = interests.range(of: "\\S+", options: .regularExpression, range: startIndex..<interests.endIndex) {
mutable.addAttribute(.backgroundColor, value: UIColor.cyan, range: NSRange(range, in: interests))
startIndex = range.upperBound
}
label.attributedText = mutable
Note: If you would like to add space around your text you can change your regex to " \\S+ " and don't forget to add spaces at the begin and at the end of your original interests string.
let attributedString = NSMutableAttributedString(string: interests)
attributedString.addAttribute(NSBackgroundColorAttributeName, value: yourColor, range: NSMakeRange(startOfWord, lengthOfWord))
What you can do is use a UICollectionView with cells that have labels that span the entire cell. Each cell then corresponds to individual words. That way you can set either the label or the cell background color to whatever you want.
For ease of implementation, I would turn your interests variable into an array of strings. That way you can easily count the number of cells you need and give each cell the right string.

Scroll down to, **and a little bit past**, a desired spot in a UITextView

What I have:
Working code that scrolls to and highlights the next instance of a search term in a UITextView that contains a moderately lengthy document.
What I want:
Instead of scrolling to the desired term so that it is at the very bottom of the TextView, I'd like to scroll slightly PAST the term so that it is better visible in the view.
Where I'm stuck:
I'm weak on how Swift generally & Swift 4 in particular work with Range, and NSRange, so while I suspect that the answer will include capturing some amount of the document beyond the search term itself (say, the lesser of the next 200 characters or the next 5 line breaks), I'm not clear how to accomplish that. Or if there is a better approach to scroll the view a bit more.
Code #1 -- does the highlighting and finds "this" instance of the search term:
func attributedTextHighlighting(instanceNbr: Int, searchTerm: String, inRawHaystackText: String) -> NSMutableAttributedString {
let desiredFont = UIFont(name: "Menlo-Regular", size: self.currentDisplayFontSize)
let fontAttribute : [NSAttributedStringKey : Any] = [NSAttributedStringKey.font: desiredFont as Any]
let attributed = NSMutableAttributedString(string: inRawHaystackText, attributes: fontAttribute)
if !searchTerm.isEmpty {
do {
let foo = NSRegularExpression.escapedPattern(for: searchTerm)
let regex = try NSRegularExpression(pattern: foo, options: .caseInsensitive)
var countingMatches = 0
for match in regex.matches(in: inRawHaystackText, range: NSRange(location: 0, length:
inRawHaystackText.utf16.count)) as [NSTextCheckingResult] {
if countingMatches == instanceNbr {
// highlight this term green
attributed.addAttribute(NSAttributedStringKey.backgroundColor, value: UIColor.green, range: match.range)
theGreenMatchingRange = match.range
} else {
// highlight this term yellow
attributed.addAttribute(NSAttributedStringKey.backgroundColor, value: UIColor.yellow, range: match.range)
}
// and either way, increment the countingMatches
countingMatches += 1
}
} catch {
fatalError("Bad RegEx! \(error)")
}
} else {
print("ofSearchTerm.isEmpty = true")
}
return attributed
}
This code finds the search term in the source text & generates attributed text that highlights the term green (for the current "find") or yellow (for all others). This code works OK.
Code #2 -- scrolls to the next instance of the search term
func scrollToNextMatchButtonTapped() {
print("scrollToNextMatchButtonTapped")
currentlyHighlightedInstance += 1
if currentlyHighlightedInstance == numberOfInstances {
currentlyHighlightedInstance = 0 // since numberOfInstances is 1-based but currentlyHighlightedInstance is 0-based
}
reloadAttribTextWithHighlighting() // calls the above code, so the green highlight moves to the next found instance
textDisplay.scrollRangeToVisible(theGreenMatchingRange) // would really like to scroll to here plus a few lines (not past the end of the document, obviously)
The last line there works (theGreenMatchingRange is the global variable that's set in Code #1 above to be the range of "this" instance of the search term), but it scrolls so that the green-highlighted term is at the very bottom of the window.
What do I need to add or do differently so that the green term isn't at the very bottom?

Programatically change some words' color in an UITextView

So I have an UITextView with some text and I want everytime the user inputs, for example, the word "while", after it has been written it should change into purple. Also, I have another UITextView just to display content, no user interaction enabled, and I want that everytime the view appears, all the "while" words to also be purple. How do I do this? Can you help me, please? Thank you!
Here's what I've tried so far:
let initialText = textView.text!
let string_to_color = "while"
let range = (initialText as NSString).range(of: string_to_color)
let attribute = NSMutableAttributedString.init(string: initialText)
attribute.addAttribute(NSForegroundColorAttributeName, value: UIColor.purple, range: range)
textView.attributedText = attribute
But it colors only the first word. This is for the user interaction disabled text field. I haven't figured out how to do it for the text field that contains the words that the user inputs.
You are not getting the range correctly, here is an example of using attributed strings in Swift 3.0:
// get initial text as a String type (you will get this from your textview)
let initialText = "Swift Attributed String"
// create an attribute for the text color, I chose blue color
let myAttribute = [ NSForegroundColorAttributeName: UIColor.blue ]
// create the attributed string and add the blue color attribute
let myString = NSMutableAttributedString(string: initialText, attributes: myAttribute )
// range starting at location 6 with a lenth of 10: "Attributed"
var myRange = NSRange(location: 6, length: 10)
// OR get range of specific string in initialText
let newRange = (initialText as NSString).range(of: "Attributed")
// change the range of the word "Attributed" to have red text color
myString.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: newRange)
// create another attribute for highlighting
let anotherAttribute = [ NSBackgroundColorAttributeName: UIColor.yellow ]
// set the range of the "Attributed" part of the string to a yellow highlight
myString.addAttributes(anotherAttribute, range: newRange)
You can use this strategy to do whatever formatting you need to do with your string. Just make sure that the range that you are getting is correct.

Attributed text swift how to change color of unattributed text

I try to make a night/day option for my app. I have a text like "<font color="#aa2c2c">Red text</font>then back to normal " .
So I need red to stay red and change only unattributed color.
Here's what I try:
text.enumerateAttributesInRange(NSRange(0..<text.length), options: []) { (attributes, range, _) -> Void in
for (attribute, object) in attributes {
NSLog("attr \(attribute) object \(object)")
}
}
}
So, log shows me something like "attr NSColor object UIDeviceRGBColorSpace 0 0 0 1" or "attr NSStrokeColor object UIDeviceRGBColorSpace 0 0 0 1". That seems to be that black text which I need to change to white.
So I put this in the loop:
if object.name!.containsString("0 0 0 1"){
text.setAttributes(NSForegroundColorAttributeName, value: UIColor.whiteColor() , range: range)
}
I'm not sure if it's the best decision, but I got error : Extra argument 'value' in call.
What's wrong with this and what is the best way to substitute color of non-attributed text only?
let text = "We tried to make this app as most intuitive as possible for you. If you have any questions don't hesitate to ask us. For a detailed manual just click here."
let linkTextWithColor = "click here"
let range = (text as NSString).rangeOfString(linkTextWithColor)
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor() , range: range)
self.helpText.attributedText = attributedString
or
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSRange(location:2,length:4))

Resources