I am trying to make fullname bold with boldFullName func. But obviously, it does not make any change on it. I believe that casting to string is deleting mutableString features. How can I avoid it without returning NSAttributedString
class NewNotificationModel: Serializable {
var fromUser: NewNotificationFromUserModel!
}
class NewNotificationFromUserModel: Serializable {
var name: String!
var lastName: String!
}
final class NewNotificationViewModel {
// MARK: Properties
var notificationModel: NewNotificationModel!
private(set) var fullName: String!
init(notificationModel: NewNotificationModel) {
self.notificationModel = notificationModel
self.fullName = boldFullName(getFullName())
private func getFullName() -> String {
guard let fromUser = notificationModel.fromUser, let name = fromUser.name, let lastname = fromUser.lastName else { return "" }
return name + " " + lastname
}
func boldFullName(_ fullname: String) -> String {
let range = NSMakeRange(0, getFullName().count)
let nonBoldFontAttribute = [NSAttributedString.Key.font:UIFont.sfProTextSemibold(size: 16)]
let boldFontAttribute = [NSAttributedString.Key.font:UIFont.catamaranBold(size: 20)]
let boldString = NSMutableAttributedString(string: getFullName() as String, attributes:nonBoldFontAttribute)
boldString.addAttributes(boldFontAttribute, range: range)
return boldString.mutableString as String
}
}
And I am using this fullname in table cell as below
class NewNotificationTableViewCell: UITableViewCell, Reusable, NibLoadable {
#IBOutlet weak var messageLbl: UILabel!
messageLbl.text = NewNotificationTableViewCell.configureText(model: model)
My configureText func is
private static func configureText(model: NewNotificationViewModel) -> String {
guard let type = model.type else { return "" }
switch NotificationType.init(rawValue: type) {
String(format:"new_notification.group.request.want.join.text_%#_%#".localized, model.fullName, model.groupName ?? "")
case .mention?: return String(format:"new_notification.post.mention_%#".localized, model.fullName)
But those .fullName does not do anything about bolding fullName.
Edited as NSAttributedString but this gives error
case .internalCommunicationMessage?: return NSAttributedString(format:("new_notification.announcement.text_%#".localized), args: NSAttributedString(string: model.groupName ?? ""))
with this extension.
public extension NSAttributedString {
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
args.forEach { (attributedString) in
let range = NSString(string: mutableNSAttributedString.string).range(of: "%#")
mutableNSAttributedString.replaceCharacters(in: range, with: attributedString)
}
self.init(attributedString: mutableNSAttributedString)
}
}
Cannot convert value of type 'String' to expected argument type 'NSAttributedString'
String doesn't contain attributes, you do need to return a NSAttributedString from your function.
What you can do instead is assigning the attributed string to the attributedText property of your UILabel. Documentation here
Example (after updating your function to return a NSAttributedString):
messageLbl.attributedText = NewNotificationTableViewCell.configureText(model: model)
You need to assign messageLbl.attributedText
func boldFullName(_ fullname: String) -> NSAttributedString {
let range = NSMakeRange(0, getFullName().count)
let nonBoldFontAttribute = [NSAttributedString.Key.font:UIFont.sfProTextSemibold(size: 16)]
let boldFontAttribute = [NSAttributedString.Key.font:UIFont.catamaranBold(size: 20)]
let boldString = NSMutableAttributedString(string: getFullName() as String, attributes:nonBoldFontAttribute)
boldString.addAttributes(boldFontAttribute, range: range)
return boldString.mutableString
}
The main problem is you are trying to give attributedString to text property which is not gonna effect on the UILabel . You must change some part of your code like :
private(set) var fullName: String!
to :
private(set) var fullName: NSMutableAttributedString!
And
func boldFullName(_ fullname: String) -> String {
let range = NSMakeRange(0, getFullName().count)
let nonBoldFontAttribute = [NSAttributedString.Key.font:UIFont.sfProTextSemibold(size: 16)]
let boldFontAttribute = [NSAttributedString.Key.font:UIFont.catamaranBold(size: 20)]
let boldString = NSMutableAttributedString(string: getFullName() as String, attributes:nonBoldFontAttribute)
boldString.addAttributes(boldFontAttribute, range: range)
return boldString.mutableString as String
}
to:
func boldFullName(_ fullname: String) -> NSMutableAttributedString {
let range = NSMakeRange(0, getFullName().count)
let nonBoldFontAttribute = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 10)]
let boldFontAttribute = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 30)]
let boldString = NSMutableAttributedString(string: getFullName() as String, attributes:nonBoldFontAttribute)
boldString.addAttributes(boldFontAttribute, range: range)
return boldString
}
And last when you call use attributedText instead of string
messageLbl.attributedText = ...
with this extension
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
args.forEach { (attributedString) in
let range = NSString(string: mutableNSAttributedString.string).range(of: "%#")
mutableNSAttributedString.replaceCharacters(in: range, with: attributedString)
}
self.init(attributedString: mutableNSAttributedString)
}
I modified my switch cases as below
case .internalCommunicationMessage?:
let content = NSAttributedString(string:"new_notification.announcement.text_%#".localized)
let gn = NSAttributedString(string: model.groupName ?? "", attributes: [.font: UIFont.sfProTextMedium(size: size),
.kern: -0.26])
return NSAttributedString(format: content, args: gn)
the changed return type of configureText
configureText(model: NewNotificationViewModel) -> NSAttributedString
deleted boldFullName function and changed fullName type back to String
and finally inserted as below to label.
messageLbl.attributedText = NewNotificationTableViewCell.configureText(model: model)
Related
Is there any way to find Hashtags from a text with SwiftUI?
This is how my try looks like:
calling the function like this : Text(convert(msg.findMentionText().joined(separator: " "), string: msg)).padding(.top, 8)
.
But it does not work at all.
My goal something like this:
extension String {
func findMentionText() -> [String] {
var arr_hasStrings:[String] = []
let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
for match in matches {
arr_hasStrings.append(NSString(string: self).substring(with: NSRange(location:match.range.location, length: match.range.length )))
}
}
return arr_hasStrings
}
}
func convert(_ hashElements:[String], string: String) -> NSAttributedString {
let hasAttribute = [NSAttributedString.Key.foregroundColor: UIColor.orange]
let normalAttribute = [NSAttributedString.Key.foregroundColor: UIColor.black]
let mainAttributedString = NSMutableAttributedString(string: string, attributes: normalAttribute)
let txtViewReviewText = string as NSString
hashElements.forEach { if string.contains($0) {
mainAttributedString.addAttributes(hasAttribute, range: txtViewReviewText.range(of: $0))
}
}
return mainAttributedString
}
You need to initailize Text() with a String, but instead you are attempting to initialize it with an Array of String.
You could either just display the first one if the array is not empty:
msg.findMentionText().first.map { Text($0) }
Or you could join the elements array into a single String:
Text(msg.findMentionText().joined(separator: " "))
I have HTML and was converted to AttributedString. Now, I need to change the generated Attributed string's font but I'm having a hard time retaining the style(Bold, Italic or Regular).
I found a solution but the problem is I don't know how to use it. They using NSMutableAttributedString as extension. I pasted my code how did I convert and the supposed solution at the bottom.
Thank you.
extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return NSAttributedString() }
do {
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return NSAttributedString()
}
}
}
import Foundation
struct Service: Codable {
var id: Int
var name: String?
var price: String?
var description: String?
var subtitle: String?
var bodyPreview: String?
var featuredImage: String? // For FindAll
var imageList: [String]? // For FindByID
private enum CodingKeys: String, CodingKey {
case id
case name
case price
case subtitle
case description
case bodyPreview = "body_preview"
case featuredImage = "featured_image_url"
case imageList = "images_url"
}
}
class ServiceDetailViewController: UIViewController {
private var service: Service?
private func showServiceDetails() {
detailLabel.attributedText = service?.description?.htmlToAttributedString
collectionView.reloadData()
startCollectionViewTimer()
}
}
Manmal's solution:
extension NSMutableAttributedString {
func setFontFace(font: UIFont, color: UIColor? = nil) {
beginEditing()
self.enumerateAttribute(
.font,
in: NSRange(location: 0, length: self.length)
) { (value, range, stop) in
if let f = value as? UIFont,
let newFontDescriptor = f.fontDescriptor
.withFamily(font.familyName)
.withSymbolicTraits(f.fontDescriptor.symbolicTraits) {
let newFont = UIFont(
descriptor: newFontDescriptor,
size: font.pointSize
)
removeAttribute(.font, range: range)
addAttribute(.font, value: newFont, range: range)
if let color = color {
removeAttribute(
.foregroundColor,
range: range
)
addAttribute(
.foregroundColor,
value: color,
range: range
)
}
}
}
endEditing()
}
}
let attriString = NSAttributedString(string:"attriString", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.lightGray,
NSAttributedString.Key.font: AttriFont])
You can simply create a similar extension to return a mutable attributed string:
extension String {
var htmlToMutableAttributedString: NSMutableAttributedString? {
do {
return try .init(data: Data(utf8), options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
print(error)
return nil
}
}
}
How do I extract hashtag strings from a text in Swift? I've seen some answers but they seem too complicated for what I need and I don't really understand how RegEx works?
E.g.
Text: "This is #something with a lot of #random #hashtags #123yay."
What I want: "something", "random", "hashtags", "123yay".
Thanks!
here is the helper method to convert your string into hash detection string
this extension find the # words from sting also including arabic words.
extension String {
func findMentionText() -> [String] {
var arr_hasStrings:[String] = []
let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
for match in matches {
arr_hasStrings.append(NSString(string: self).substring(with: NSRange(location:match.range.location, length: match.range.length )))
}
}
return arr_hasStrings
}
}
And below method converts your string into Reach colorful hash string.
func convert(_ hashElements:[String], string: String) -> NSAttributedString {
let hasAttribute = [NSAttributedStringKey.foregroundColor: UIColor.orange]
let normalAttribute = [NSAttributedStringKey.foregroundColor: UIColor.black]
let mainAttributedString = NSMutableAttributedString(string: string, attributes: normalAttribute)
let txtViewReviewText = string as NSString
hashElements.forEach { if string.contains($0) {
mainAttributedString.addAttributes(hasAttribute, range: txtViewReviewText.range(of: $0))
}
}
return mainAttributedString
}
i.e
let text = "#Jaydeep #Viral you have to come for party"
let hashString = convert(text.findMentionText(), string: text)
Output:
extension String
{
func hashtags() -> [String]
{
if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive)
{
let string = self as NSString
return regex.matches(in: self, options: [], range: NSRange(location: 0, length: string.length)).map {
string.substring(with: $0.range).replacingOccurrences(of: "#", with: "").lowercased()
}
}
return []
}
}
then, to get the hashtags array
yourstring.hashtags()
Here is the source
let str = "This is #something with a lot of #random #hashtags #123yay."
let words = str.components(separatedBy: " ")
var hashTags = [String]()
for word in words{
if word.hasPrefix("#"){
let hashtag = word.dropFirst()
hashTags.append(String(hashtag))
}
}
print("Hashtags :: ", hashTags)
First things first, this works best in a TextView. So set one up inside of your view however you want, but make sure that your ViewController has a UITextViewDelegate & the textView is delegated to that view controller.
I’m also doing this with some prefilled information, but the same concept applies with pulling data from your database and what not.
This is how we set up our ViewController:
class ViewController: UIViewController, UITextViewDelegate {
var string = "Hello, my name is #Jared & #Jared and I like to move it."
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.text = string
textView.delegate = self
}
The overall task we’re trying to accomplish in this part is just to split up all the words in our textView. It’s simpler than you might think:
First, let’s create our extension:
Now add this to your ViewController:
extension UITextView {
func resolveTags(){
let nsText:NSString = self.text as NSString
let words:[String] = nsText.components(separatedBy: " ")
let attrs = [
NSAttributedStringKey.font : UIFont.init(name: "HelveticaNeue", size: 13),
NSAttributedStringKey.foregroundColor : UIColor.black
]
let attrString = NSMutableAttributedString(string: nsText as String, attributes:attrs)
for word in words {
if word.hasPrefix("#") {
let matchRange:NSRange = nsText.range(of: word as String)
var stringifiedWord:String = word as String
stringifiedWord = String(stringifiedWord.dropFirst())
attrString.addAttribute(NSAttributedStringKey.link, value: "hash:\(stringifiedWord)", range: matchRange)
attrString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue , range: matchRange)
}
}
self.attributedText = attrString
}
}
Let’s use this thing!
It all comes down to this. We have this function working, now how do we use it?
Easy.
Inside of your viewDidLoad function, or wherever you set your textView text, just call:
textView.resolveTags()
Result:
Courtesy of: Jared Davidson On Twitter
You can also use third party Activelabel . this is simple to use and also support Hashtags (#), Mentions (#), URLs (http://) and custom regex patterns
https://github.com/optonaut/ActiveLabel.swift
I just changed #JayDeep 's answer to more swifty style.
extension String {
var tags: [String] {
let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
let nsRange: NSRange = .init(location: 0, length: self.count)
guard let matches = regex?.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: nsRange)
else { return [] }
return matches
.map { match in
let startIndex = self.index(self.startIndex, offsetBy: match.range.location)
let endIndex = self.index(startIndex, offsetBy: match.range.length)
let range = startIndex ..< endIndex
return String(self[range])
}
}
}
My clean solution: We will return PrefixesDetected to the view. And the view will format it as he wants. (So we will execute yourString.resolvePrefixes()) in the viewModel and we will be able to test it.
struct PrefixesDetected {
let text: String
let prefix: String?
}
extension String {
func resolvePrefixes(_ prefixes: [String] = ["#", "#"]) -> [PrefixesDetected] {
let words = self.components(separatedBy: " ")
return words.map { word -> PrefixesDetected in
PrefixesDetected(text: word,
prefix: word.hasPrefix(prefixes: prefixes))
}
}
func hasPrefix(prefixes: [String]) -> String? {
for prefix in prefixes {
if hasPrefix(prefix) {
return prefix
}
}
return nil
}
}
Then in the view we can format it as for example: (In this case we want both in the same color but in this way you can give them different behaviors)
Here I do with reduce but this is just to show an example, you can format it as you want! :)
titleDetectedPrefixes.reduce(NSAttributedString(), { result, prefixDectedWord in
let wordColor: UIColor = prefixDectedWord.prefix != nil ? .highlightTextMain : .mainText
let attributedWord = NSAttributedString(string: prefixDectedWord.text)
{ Add desired attributes }
})
for those who are using swiftUI you can achieve it by using the "+" operator
so the final solution will look like this
static func tagHighlighter(description : String , previousText : Text = Text("") , tag : String = "#") -> Text {
var t : Text = Text("")
let words : [String] = description.components(separatedBy: " ")
for word in words {
if !word.isEmpty {
let tag = word[word.startIndex]
if tag == "#" {
t = t + Text("\(word) ").foregroundColor(Color("tag_color"))
} else if tag == "#" {
t = t + Text("\(word) ").foregroundColor(Color("tag_color"))
} else {
t = t + Text("\(word) ")
}
}
}
return t
}
This is how I'm doing it
private func getHashTags(from caption: String) -> [String] {
var words: [String] = []
let texts = caption.components(separatedBy: " ")
for text in texts.filter({ $0.hasPrefix("#") }) {
if text.count > 1 {
let subString = String(text.suffix(text.count - 1))
words.append(subString.lowercased())
}
}
return words
}
Copy paste this extension to your class:
extension UITextView{
func insertTextWithHashtags(text textString: String){
let nsTextString: NSString = textString as NSString
let simpleTextAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Black Color")!, NSAttributedString.Key.font : UIFont(name: "Inter-Regular", size: 16.0)!]
let attributedString = NSMutableAttributedString(string: textString, attributes: simpleTextAttributes)
var word = ""
for text in textString+" "{ //+" " is for loop to run one extra time to complete hashtag
if text == "#" || text == "\n" || text == " "{
if word.hasPrefix("#"){
let range = nsTextString.range(of: word)
let link = [NSAttributedString.Key.link : word]
attributedString.addAttributes(link, range: range)
if text == "#"{
word = "#"
}else{
word = ""
}
}else{
if text == "#"{
word = "#"
}
}
}else{
if word.hasPrefix("#"){
word.append(text)
}
}
}
//For for applying attributes to hashtag
let linkAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Primary Color")!]
self.linkTextAttributes = linkAttributes
self.attributedText = attributedString
}
}
and then call it like this:
postTextView.insertTextWithHashtags(text: "#Hello#Hey #Space")
I want to display in an attributed string 2 links, each link with a different color. I do not understand how to do that. It will always set just one color. I've been struggling with this for days and still can't figure out how to make it work. Does anybody know? I can set two colors but not for links! All links are the same color.
This is my whole implementation: (UPDATE)
var checkIn = ""
var friends = ""
//MARK: Change Name Color / Font / Add a second LABEL into the same label
func setColorAndFontAttributesToNameAndCheckIn() {
let nameSurname = "\(postAddSetup.nameSurname.text!)"
checkIn = ""
friends = ""
if selectedFriends.count == 0 {
print("we have no friends...")
friends = ""
} else if selectedFriends.count == 1 {
print("we have only one friend...")
friends = ""
friends = " is with \(self.firstFriendToShow)"
} else if selectedFriends.count > 1 {
print("we have more than one friend...")
friends = ""
friends = " is with \(self.firstFriendToShow) and \(self.numberOfFriendsCount) more"
}
if checkIn == "" {
checkIn = ""
}
var string = postAddSetup.nameSurname.text
string = "\(nameSurname)\(friends)\(checkIn) "
let attributedString = NSMutableAttributedString(string: string!)
attributedString.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFont(ofSize: 14), range: (string! as NSString).range(of: nameSurname))
attributedString.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 13), range: (string! as NSString).range(of: checkIn))
attributedString.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 13), range: (string! as NSString).range(of: friends))
attributedString.addLink("checkIn", linkColor: UIColor.darkGray, text: checkIn)
attributedString.addLink("tagFriends", linkColor: UIColor.red, text: friends)
//attributedString.addAttribute(NSLinkAttributeName, value: "checkIn", range: (string! as NSString).range(of: checkIn))
//attributedString.addAttribute(NSLinkAttributeName, value: "tagFriends", range: (string! as NSString).range(of: friends))
//postAddSetup.nameSurname.linkTextAttributes = [NSForegroundColorAttributeName:UIColor.redIWorkOut(), NSFontAttributeName: UIFont.systemFont(ofSize: 13)]
//attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.darkGray, range: (string! as NSString).range(of: checkIn))
postAddSetup.nameSurname.attributedText = attributedString
print("atribute: \(attributedString)")
}
func string1Action() {
print("action for string 1...")
}
func string2Action() {
print("action for string 2...")
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if URL.absoluteString == "string1" {
string1Action()
} else if URL.absoluteString == "string2" {
string2Action()
}
return false
}
extension NSMutableAttributedString {
func addLink(_ link: String, linkColor: UIColor, text: String) {
let pattern = "(\(text))"
let regex = try! NSRegularExpression(pattern: pattern,
options: NSRegularExpression.Options(rawValue: 0))
let matchResults = regex.matches(in: self.string,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSRange(location: 0, length: self.string.characters.count))
for result in matchResults {
self.addAttribute(NSLinkAttributeName, value: link, range: result.rangeAt(0))
self.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: result.rangeAt(0))
}
}
}
I have used in a project this NSMutableAttributedString extension adapted from this Article.
Using NSRegularExpression you can assign your respective color matching the range of your link text:
The extension:
extension NSMutableAttributedString {
func addLink(_ link: String, linkColor: UIColor, text: String) {
let pattern = "(\(text))"
let regex = try! NSRegularExpression(pattern: pattern,
options: NSRegularExpression.Options(rawValue: 0))
let matchResults = regex.matches(in: self.string,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSRange(location: 0, length: self.string.characters.count))
for result in matchResults {
self.addAttribute(NSLinkAttributeName, value: link, range: result.rangeAt(0))
self.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: result.rangeAt(0))
}
}
}
Edit:
Set a custom UITextView class to use this extension and using the delegate function shouldInteractWith url it’s possible to simulate the hyperlink logic of UITextView:
class CustomTextView: UITextView {
private let linksAttributes = [NSLinkAttributeName]
override func awakeFromNib() {
super.awakeFromNib()
let tapGest = UITapGestureRecognizer(target: self, action: #selector(self.onTapAction))
self.addGestureRecognizer(tapGest)
}
#objc private func onTapAction(_ tapGest: UITapGestureRecognizer) {
let location = tapGest.location(in: self)
let charIndex = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if charIndex < self.textStorage.length {
var range = NSMakeRange(0, 0)
for linkAttribute in linksAttributes {
if let link = self.attributedText.attribute(linkAttribute, at: charIndex, effectiveRange: &range) as? String {
guard let url = URL(string: link) else { return }
_ = self.delegate?.textView?(self, shouldInteractWith: url, in: range, interaction: .invokeDefaultAction)
}
}
}
}
}
How to use:
attributedString.addLink(yourLinkUrl, linkColor: yourLinkColor, text: yourLinkText)
let textView = CustomTextView()
textView.attributedText = attributedString
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