I'm trying to apply formatting on the selected range of the textview. The problem is when the selected text format applied, the rest of text reset its format.
Here is my code:
if let text = textView.text {
if let textRange = textView.selectedTextRange {
if let selectedText = textView.text(in: textRange) {
let range = (text as NSString).range(of: selectedText)
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 18) , range: range)
self.textView.attributedText = attributedString
}
}
}
let range = textView.selectedRange
let string = NSMutableAttributedString(attributedString:
textView.attributedText)
let attributes = [NSForegroundColorAttributeName: UIColor.redColor()]
string.addAttributes(attributes, range: textView.selectedRange)
textView.attributedText = string
textView.selectedRange = range
Related
I want to highlight and unhighlight the searched text in UITableView.
For Highlighting the searched text, i have tried below code in CellForRow and Highlighting searched text works fine.
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of: self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor: Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
EDIT 1
if searchBar.text != "" {
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of: self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor: Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
}
else {
cell.lblName.text = self.modelClass.users[indexPath.row].name
}
EDIT 2
Actually me 1st code is working fine. First i assign normal text to lblName.text and then after again i set attributedText to lblName.attributedText. When i comment the normal text assign, it works fine.
But when i clear searched data and load my default array but the highlighted color wasn't clear in lblName UILabel.
Please guide me how can i unhighlight the UILabel text?
in cellForRowAt, You also need to pass an else condition for the default label view.
if self.searchBar.text != "" {
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of:
self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string:
self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor:
Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
}
else {
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name as NSString)
attributeString.removeAttribute(NSAttributedString.Key.backgroundColor, range: NSMakeRange(0, attributeString.length))
cell.lblName.attributedText = attributeString
}
Reload TableView when searchBar text changes.
I wrote an extension for UILabel to manage this
extension UILabel {
func stringWithSearchBarString(_ string: String) {
let attributedString = NSMutableAttributedString(string: text ?? "")
if string.count == 0 {
attributedText = attributedString
return
}
let dotRanges: [NSRange]
do {
let regex = try NSRegularExpression(pattern: string.lowercased(), options: [])
dotRanges = regex.matches(in: string.lowercased(), options: [], range: NSMakeRange(0, string.count)).map {$0.range}
} catch {
dotRanges = []
}
let rangeColor = WXColors.mainAppColor.color
for dotRange in dotRanges {
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: rangeColor, range: dotRange)
}
attributedText = attributedString
}
}
USAGE:
cell.textLabel.stringWithSearchBarString(searcher.text)
My code not working with NSRange location on iOS13, on iOS12 and below it's work. Is there a way to color the text from the letters to be colored until the total letters afterwards? because I only have data starting from the colored letters and the total letters afterwards.
override func viewDidLoad() {
super.viewDidLoad()
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
let dataStartIHave = 0
let dataTotalIHave = 7
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: dataStartIHave, length: dataTotalIHave))
attributedLabel.attributedText = attributedString
}
Thanks in advance.
To get the range from the end of the word until the end of the text reliably you have to convert NSRange to Range<String.Index> and back
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
let dataStartIHave = 0
let dataTotalIHave = 7
let wordRange = NSRange(location: dataStartIHave, length: dataTotalIHave)
let upperBound = Range(wordRange, in: text)!.upperBound
let upperRange = NSRange(upperBound..., in: text)
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: upperRange)
attributedLabel.attributedText = attributedString
In Swift it's more efficient to get the Range<String.Index> from the word
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
if let testingRange = text.range(of: "Testing") {
let upperRange = NSRange(testingRange.upperBound..., in: text)
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: upperRange)
}
attributedLabel.attributedText = attributedString
This will work:
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: dataStartIHave, length: dataTotalIHave))
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
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
}
}
How do you change the color of specific texts within an array of string that's going to be passed into a label?
Let's say I have an array of string:
var stringData = ["First one", "Please change the color", "don't change me"]
And then it's passed to some labels:
Label1.text = stringData[0]
Label2.text = stringData[1]
Label3.text = stringData[2]
What's the best approach to change the color of the word "the" in stringData[1]?
Thank you in advance for your help!
let str = NSMutableAttributedString(string: "Please change the color")
str.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: NSMakeRange(14, 3))
label.attributedText = str
The range is the range of the specific text.
If you want to change the color of all the in your string:
func highlight(word: String, in str: String, with color: UIColor) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: str)
let highlightAttributes = [NSForegroundColorAttributeName: color]
let nsstr = str as NSString
var searchRange = NSMakeRange(0, nsstr.length)
while true {
let foundRange = nsstr.range(of: word, options: [], range: searchRange)
if foundRange.location == NSNotFound {
break
}
attributedString.setAttributes(highlightAttributes, range: foundRange)
let newLocation = foundRange.location + foundRange.length
let newLength = nsstr.length - newLocation
searchRange = NSMakeRange(newLocation, newLength)
}
return attributedString
}
label2.attributedText = highlight(word: "the", in: stringData[1], with: .red)
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
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)
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
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