Make Ints within a string bold - swift - swift

Hi I have a string that I'm displaying in a label that shows a time like this "4hours 27mins 45secs" and this can vary based on the time for example "30mins 5secs"
I want to style the Ints in the string so they are bold and the characters are light so it looks like this...
I've done some searching and found I need to use some sort of attributed string, but everything I've found seems to use some sort of breaking character, or a standard prefix to detect.
As I don't have either is there a way to check if the character is an int and apply bold. Appreciate this might not be possible and would welcome a recommendation for changing my approach.
Still pretty new to coding/swift so any help appreciated!

Quickly done, you can use a Regular Expression to find the ranges of all the numbers. Then, on these ranges, you change the attribute for a bold font.
let attributedString = NSMutableAttributedString(string: str, attributes: [.foregroundColor: UIColor.white])
let regex = try! NSRegularExpression(pattern: "\\d+", options: [])
let matches = regex.matches(in: attributedString.string, options: [], range: NSRange(location: 0, length: attributedString.length))
matches.forEach {
attributedString.addAttributes([.font: UIFont.boldSystemFont(ofSize: 17)], range: $0.range)
}

If your deployment target is iOS15 you can use the newly added AttributedString with markdown support, which is much simple than the very complicated to use NSAttributedString.
https://developer.apple.com/documentation/foundation/attributedstring

Related

Find and replace characters in attributed string with attributes?

I am facing an issue with replacing some characters in an attributed string with some attributed characters.
let replaceString = NSMutableAttributedString(string: playerNames[index], attributes: attributes)
taskText.mutableString.replaceOccurrences(of: searchString[Int(index)], with: playerNames[index], options: .caseInsensitive, range: NSRange(location: 0, length: taskText.length))
The problem is, that I can't add the replaceString to the replaceOccurrences function because I can't use attributed strings here. Is there any method other method?
I just want to replace some "#1" and "#2" strings with some names like "Anna" but with bold font to make it more visible.

Swift replace occurrence of string with condition

I have string like below
<p><strong>I am a strongPerson</strong></p>
I want to covert this string like this
<p><strong>I am a weakPerson</strong></p>
When I try below code
let old = "<p><strong>I am a strongPerson</strong></p>"
let new = old.replacingOccurrences(of: "strong", with: "weak")
print("\(new)")
I am getting output like
<p><weak>I am a weakPerson</weak></p>
But I need output like this
<p><strong>I am a weakPerson</strong></p>
My Condition here is
1.It has to replace only if word does not contain these HTML Tags like "<>".
Help me to get it. Thanks in advance.
You can use a regular expression to avoid the word being in a tag:
let old = "strong <p><strong>I am a strong person</strong></p> strong"
let new = old.replacingOccurrences(of: "strong(?!>)", with: "weak", options: .regularExpression, range: nil)
print(new)
I added some extra uses of the word "strong" to test edge cases.
The trick is the use of (?!>) which basically means to ignore any match that has a > at the end of it. Look at the documentation for NSRegularExpression and find the documentation for the "negative look-ahead assertion".
Output:
weak <p><strong>I am a weak person</strong></p> weak
Try the following:
let myString = "<p><strong>I am a strongPerson</strong></p>"
if let regex = try? NSRegularExpression(pattern: "strong(?!>)") {
let modString = regex.stringByReplacingMatches(in: myString, options: [], range: NSRange(location: 0, length: myString.count), withTemplate: "weak")
print(modString)
}

Swift finding and changing range in attributed string

What would be the best way to change this string:
"gain quickness for 5 seconds. <c=#reminder>(Cooldown: 90s)</c> only after"
into an Attributed String while getting rid of the part in the <> and I want to change the font of (Cooldown: 90s). I know how to change and make NSMutableAttributedStrings but I am stuck on how to locate and change just the (Cooldown: 90s) in this case. The text in between the <c=#reminder> & </c> will change so I need to use those to find what I need.
These seem to be indicators meant to be used for this purpose I just don't know ho.
First things first, you'll need a regular expression to find and replace all tagged strings.
Looking at the string, one possible regex could be <c=#([a-zA-Z-9]+)>([^<]*)</c>. Note that will will work only if the string between the tags doesn't contain the < character.
Now that we have the regex, we only need to apply it on the input string:
let str = "gain quickness for 5 seconds. <c=#reminder>(Cooldown: 90s)</c> only after"
let attrStr = NSMutableAttributedString(string: str)
let regex = try! NSRegularExpression(pattern: "<c=#([a-zA-Z-9]+)>([^<]*)</c>", options: [])
while let match = regex.matches(in: attrStr.string, options: [], range: NSRange(location: 0, length: attrStr.string.utf16.count)).first {
let indicator = str[Range(match.range(at: 1), in: str)!]
let substr = str[Range(match.range(at: 2), in: str)!]
let replacement = NSMutableAttributedString(string: String(substr))
// now based on the indicator variable you might want to apply some transformations in the `substr` attributed string
attrStr.replaceCharacters(in: match.range, with: replacement)
}

Setting foreground color works only once for NSAttributedString

The phone part of the string gets the underline attribute, but the color remains red. I've separated the color and underline setAttributes() calls, to make things clear, the same happens when it's one call.
let text = "call "
let phone = "1800-800-900"
let attrString = NSMutableAttributedString(string: text + phone, attributes: nil)
let rangeText = (attrString.string as NSString).range(of: text)
let rangePhone = (attrString.string as NSString).range(of: phone)
attrString.setAttributes([NSAttributedStringKey.foregroundColor: UIColor.red],
range: rangeText)
attrString.setAttributes([NSAttributedStringKey.foregroundColor: UIColor.blue],
range: rangePhone)
attrString.setAttributes([NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue],
range: rangePhone)
From the doc of setAttributes():
These new attributes replace any attributes previously associated with
the characters in aRange.
So in other words, it replace them, erasing all previously set, so when you add the underline, it removes the color at that range.
Solution, use addAttributes() instead of setAttributes():
let text = "call "
let phone = "1800-800-900"
let attrString = NSMutableAttributedString(string: text + phone, attributes: nil)
let rangeText = (attrString.string as NSString).range(of: text)
let rangePhone = (attrString.string as NSString).range(of: phone)
attrString.addAttributes([NSAttributedStringKey.foregroundColor: UIColor.red],
range: rangeText)
attrString.addAttributes([NSAttributedStringKey.foregroundColor: UIColor.blue],
range: rangePhone)
attrString.addAttributes([NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue],
range: rangePhone)
Other solution, use two NSAttributedString (I also remove the NSAttributedStringKey in the enum)
let textAttrStr = NSAttributedString(string:text, attributes:[.foregroundColor: UIColor.red])
let phoneAttrStr = NSAttributedString(string:phone, attributes:[.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.styleSingle.rawValue])
let finalAttrStr = NSMutableAttributedString.init(attributedString: textAttrStr)
finalAttrStr.append(phoneAttrStr)
Possible issue with the first solution:
range(of:) returns the range of the first occurence only.
In other words, if text = "1800 " and phone = "18", you'll get unwanted results. because rangePhone would be from index 0 to 1, and not 7 to 8 in 1800 18. This issue won't happen in the second one.
You should not separate the second and the third call of setAttributes, since the latter will overwrite the earlier. Combine the styles in one array:
attrString.setAttributes([.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.styleSingle.rawValue],
range: rangePhone)
Result:

Swift 3 replacingOccurrences of multiple strings

I have JavaScript code that looks like the following:
foo.replace(/(\r\n|\n|\r)/gm, "");
Basically this strips any return carriages and new lines from my string. I'd like to do something similar in Swift 3.
I see func replacingOccurrences(of target: String, with replacement: String, options: CompareOptions = default, range searchRange: Range<Index>? = default) -> String is available. The problem is, this only takes one string in the of parameter.
Does this mean I need to call the method multiple times for each of my instances above, once for \r\n, once for \n\ and once for \r? Is there anyway to potentially accomplish something closer to what the regex is doing instead of calling replacingOccurrences three times?
Use replacingOccurrences with the option set to regularExpression.
let updated = foo.replacingOccurrences(of: "\r\n|\n|\r", with: "", options: .regularExpression)
To replace non-digits you can use "\D" as a regular expression
let numbers = "+1(202)-505-71-17".replacingOccurrences(of: "\\D", with: "", options: .regularExpression)
You can find how to use other regular expressions here
This one is pretty easy, it's all in the documentation.
let noNewlines = "a\nb\r\nc\r".replacingOccurrences(of: "\r\n|\n|\r", with: "", options: .regularExpression)