Range position of letter in string duplicating [duplicate] - swift

This question already has answers here:
Index of a substring in a string with Swift
(11 answers)
Closed 4 years ago.
I have a string like "abc1abc1".
What I want to do is bold each number of the string. I have drawn the code below. It works by separating each character of the string and putting them into an array. Then, in a loop, if each character contains an Int(), the character is bolded.
However, the issue comes when the there are two of the same Int. In the string above, there are 2 characters of 1, therefore the code will only bold the first character.
// Bold the numbers
let fullString = "abc1abc1"
let characters = Array(fullString)
let mutableString = NSMutableAttributedString(string: fullString)
for item in characters {
let string = String(item)
let decimalCharacters = CharacterSet.decimalDigits
let decimalRange = string.rangeOfCharacter(from: decimalCharacters)
if decimalRange != nil {
let str = NSString(string: fullString)
let range = str.range(of: string)
mutableString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIFont.systemFont(ofSize: 18, weight: .heavy), range: range)
}
}
instructionsLabel.attributedText = mutableString
// characters = ["a", "b", "c", "1", "a", "b", "c", "1"]
// range of ints returns {3, 1} and {3, 1}
// range of ints SHOULD return {3, 1} and {7, 1}

Try this:
let fullString = "abc1abc1"
let range = NSRange(location: 0, length: (fullString as NSString).length)
let mutableString = NSMutableAttributedString(string: fullString)
let regex = try! NSRegularExpression(pattern: "[0-9]")
let matches = regex.matches(in: fullString, range: range)
for match in matches {
mutableString.addAttributes([.font: UIFont.systemFont(ofSize: 18, weight: .heavy)], range: match.range)
}
instructionsLabel.attributedText = mutableString

Related

Return a dictionary from multiple line string in swift

I am new to swift. Is there a simple way to convert the following string to dictionary format.
Example:
input = "Name: Jack\\n Area: Place\\n FavColor: Blue\\n"
Expected Output:
dict = \[Name: Jack, Area: Place, FavColor: Blue\]
and also leading spaces should be trimmed.
The key-value pairs are always in new line.
My idea is split the string based on new line characters first
lines = input.components(separatedBy: CharacterSet.newlines)
then iterate through lines and split each line based on ":" and put them into dict.
Is there any other efficient way to do it?
A possible solution is Regular Expression, it searches for the pattern word - colon - whitespace(s) - word and captures both words
let input = "Name: Jack\n Area: Place\n FavColor: Blue\n"
let pattern = "(\\b.*\\b):\\s+(\\b.*\\b)" // or "(\\w+):\\s+(\\w+)"
let regex = try! NSRegularExpression(pattern: pattern)
var result = [String:String]()
regex.matches(in: input).forEach { match in
if let keyRange = Range(match.range(at: 1), in: input),
let valueRange = Range(match.range(at: 2), in: input) {
result[String(input[keyRange])] = String(input[valueRange])
}
}
print(result)
My usual approach is to split the string into an array of strings and use the position to determine if the element is a key or value. After that use stride to loop through the array two element at a time to assign the output dictionary where the first element is the key and the second element is the value.
let input = "Name: Jack\n Area: Place\n FavColor: Blue\n"
let charSet = CharacterSet(charactersIn: "\n:")
let tokenized = input.components(separatedBy: charSet).map {
$0.trimmingCharacters(in: .whitespaces)
}
var dict = [String : String]()
for index in stride(from: 0, to: tokenized.count - 1, by: 2) {
dict[tokenized[index]] = tokenized[index + 1]
}
let input = "Name: Jack\n Area: Place\n FavColor: Blue\n Link:
https://example.com".replacingOccurrences(of: " +", with: " ", options: .regularExpression,
range: nil)
let components = input.replacingOccurrences(of: "\n", with: ":").components(separatedBy: ": ")
var dictionary = [String:String]()
for index in stride(from: 0, to: components.count, by: 2){
dictionary[components[index]] = components[index + 1]
}

If the text contains emoji, Range is nil from swift

The text "Welcome my application..❣️" does not make sense during the NSRange and Range tests. If ❣️ is included, Range is returned as nil, and I wonder why.
func testA() {
let testStr = "Welcome my application..❣️"
let range = NSRange(location: 0, length: testStr.count)
let wrapRange = Range(range, in: testStr)
let testStrB = "Welcome my application.."
let rangeB = NSRange(location: 0, length: testStrB.count)
let wrapRangeB = Range(rangeB, in: testStrB)
print("wrapRange: \(wrapRange) wrapRangeB: \(wrapRangeB)")
}
RESULT:
wrapRange: nil wrapRangeB: Optional(Range(Swift.String.Index(_rawBits: 1)..<Swift.String.Index(_rawBits: 1572864)))
"❣️" is a single “extended grapheme cluster”, but two UTF-16 code units:
print("❣️".count) // 1
print("❣️".utf16.count) // 2
NSRange counts UTF-16 code units (which are the “characters” in an NSString) , therefore the correct way to create an NSRange comprising the complete range of a Swift string is
let range = NSRange(location: 0, length: testStr.utf16.count)
or better (since Swift 4):
let range = NSRange(testStr.startIndex..., in: testStr)
Explanation: In your code (simplified here)
let testStr = "❣️"
let range = NSRange(location: 0, length: testStr.count)
print(range) // {0, 1}
creates an NSRange describing a single UTF-16 code unit. This cannot be converted to a Range<String.Index> in testStr because its first Character consists of two UTF-16 code units:
let wrapRange = Range(range, in: testStr)
print(wrapRange) // nil

split string into array with a common regx

In Swift I have a string like:
let str = "5~895893799,,6~898593679,,7~895893679,,8~895893799,,5~895893799,,6~898593679,,7~895893679,,8~895893799";
From this I need to get only the number which is before "~" which is [5,6,7,8,5,6,7,8]
How can I achieve this?
Make use of components(separatedBy:) and compactMap.
let str = "5~895893799,,6~898593679,,7~895893679,,8~895893799,,5~895893799,,6~898593679,,7~895893679,,8~895893799"
let nums = str.components(separatedBy: ",,")
.compactMap { $0.components(separatedBy: "~").first }
That gives the string array:
["5", "6", "7", "8", "5", "6", "7", "8"]
If you want an array of Int, add:
.compactMap { Int($0) }
to the end.
let nums = str.components(separatedBy: ",,")
.compactMap { $0.components(separatedBy: "~").first }
.compactMap { Int($0) }
That gives:
[5, 6, 7, 8, 5, 6, 7, 8]
You could define this regular expression:
let regex = try! NSRegularExpression(pattern: #"\d+(?=~)"#)
\d : matches any digit
+ : this is the one or more quantifier
(?=~) : Positive lookahead for "~"
and use it like so:
let range = NSRange(location: 0, length: (str as NSString).length)
let matches = regex.matches(in: str, range: range)
let strings: [String] = matches.map { match in
let subRange = match.range
let startIndex = str.index(str.startIndex, offsetBy: subRange.lowerBound)
let endIndex = str.index(str.startIndex, offsetBy: subRange.upperBound)
let subString = String(str[startIndex..<endIndex])
return subString
}
let numbers = strings.compactMap(Int.init) //[5, 6, 7, 8, 5, 6, 7, 8]

How can I substring this string?

how can I substring the next 2 characters of a string after a certian character. For example I have a strings str1 = "12:34" and other like str2 = "12:345. I want to get the next 2 characters after : the colons.
I want a same code that will work for str1 and str2.
Swift's substring is complicated:
let str = "12:345"
if let range = str.range(of: ":") {
let startIndex = str.index(range.lowerBound, offsetBy: 1)
let endIndex = str.index(startIndex, offsetBy: 2)
print(str[startIndex..<endIndex])
}
It is very easy to use str.index() method as shown in #MikeHenderson's answer, but an alternative to that, without using that method is iterating through the string's characters and creating a new string for holding the first two characters after the ":", like so:
var string1="12:458676"
var nr=0
var newString=""
for c in string1.characters{
if nr>0{
newString+=String(c)
nr-=1
}
if c==":" {nr=2}
}
print(newString) // prints 45
Hope this helps!
A possible solution is Regular Expression,
The pattern checks for a colon followed by two digits and captures the two digits:
let string = "12:34"
let pattern = ":(\\d{2})"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.characters.count)) {
print((string as NSString).substring(with: match.rangeAt(1)))
}

Calculate range of string from word to end of string in swift

I have an NSMutatableString:
var string: String = "Due in %# (%#) $%#.\nOverdue! Please pay now %#"
attributedText = NSMutableAttributedString(string: string, attributes: attributes)
How to calculate both the length and starting index from the word Overdue in swift?
so far I have tried:
let startIndex = attributedText.string.rangeOfString("Overdue")
let range = startIndex..<attributedText.string.finishIndex
// Access the substring.
let substring = value[range]
print(substring)
But it doesn't work.
You should generate the resulting string first:
let string = String(format: "Due in %# (%#) $%#.\nOverdue! Please pay now %#", "some date", "something", "15", "some date")
Then use .disTanceTo to get the distance between indices;
if let range = string.rangeOfString("Overdue") {
let start = string.startIndex.distanceTo(range.startIndex)
let length = range.startIndex.distanceTo(string.endIndex)
let wordToEndRange = NSRange(location: start, length: length)
// This is the range you need
attributedText.addAttribute(NSForegroundColorAttributeName,
value: UIColor.blueColor(), range: wordToEndRange)
}
Please do note that NSRange does not work correctly if the string contains Emojis or other Unicode characters so that the above solution may not work properly for that case.
Please look at the following SO answers for a better solution which cover that case as well:
https://stackoverflow.com/a/27041376/793428
https://stackoverflow.com/a/27880748/793428
Look at this,
let nsString = element.text as NSString
let range = nsString.range(of: word, options: .widthInsensitive)
let att: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.systemBlue]
attText.addAttributes(att, range: NSRange(location: range.location, length: range.length))