How do you add an attribute to an Unicode string in Swift? - unicode

How do you add attribute to a UNICODE string in Swift?
NSMakeRange seems to expect String in bytes for a variable byte length UNICODE string.
Any way to solve this? Thanks in advance.
var s:NSMutableAttributedString = NSMutableAttributedString(string: "வாங்க வாங்க வணக்கமுங்க")
s.string[0...2]
s.addAttribute(NSForegroundColorAttributeName, value:UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0), range:NSMakeRange(0,3))
UPDATED EXAMPLE:
let text = "வாங்க வாங்க வணக்கமுங்க"
var startOfWord = advance(text.startIndex, 4)
var endOfWord = advance(startOfWord, 3)
var highlightRange = startOfWord..<endOfWord
text[startOfWord..<endOfWord]
let attrText = NSMutableAttributedString(string:text)
attrText.addAttribute(NSForegroundColorAttributeName, value:UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0), range:highlightRange)
How do construct NSRange from swift Range?

I created a small extension for String which works for both Index based and Int based Ranges.
extension String {
func NSRangeFromRange(swiftRange: Range<Index>) -> NSRange {
let start = swiftRange.startIndex.samePositionIn(utf16)
let end = swiftRange.endIndex.samePositionIn(utf16)
return NSRange(location: utf16.startIndex.distanceTo(start), length: start.distanceTo(end))
}
func NSRangeFromRange(swiftRange: Range<Int>) -> NSRange {
let indexRange = Range(start: startIndex.advancedBy(swiftRange.startIndex), end: startIndex.advancedBy(swiftRange.endIndex))
return NSRangeFromRange(indexRange)
}
}
Note: The function is on the String class because we need the strings UTF16View to convert between the Unicode-aware Range<Index> and the non-Unicode-aware NSRange.

I hope the below function may help you
func findAndAddAttributeString(str: String, query : String) {
let strASNSString = str as NSString
println("employeeIdAsNSString, \(strASNSString)")
var attributeDictionary = NSMutableDictionary(objects: [UIColor.grayColor(), UIFont.systemFontOfSize(17)], forKeys: [NSForegroundColorAttributeName, NSFontAttributeName])
println("attributeDictionary, \(attributeDictionary)")
var attributedEmployeeId = NSMutableAttributedString(string: strASNSString, attributes: attributeDictionary)
println("attributedEmployeeId = \(attributedEmployeeId)")
var error:NSError?
var regex = NSRegularExpression.regularExpressionWithPattern(query, options: NSRegularExpressionOptions.CaseInsensitive, error: &error)
println("regex = \(regex)")
var range = NSMakeRange(0, strASNSString.length)
println("range = \(range)")
regex.enumerateMatchesInString(strASNSString, options: nil, range: range, usingBlock:{ (textCheckingResult, MatchingFlags, unsafePointer) in
println("textCheckingResult \(textCheckingResult.rangeAtIndex(0))")
var subStringRange = textCheckingResult.rangeAtIndex(0)
attributedEmployeeId.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: subStringRange)
})
}

You can convert a swift String to Swift NSString by
var strAsNSString = "வாங்க வாங்க வணக்கமுங்க" as NSString
And then you can create NSMutableAttributedString.

Something like this might work:
let text = "வாங்க வாங்க வணக்கமுங்க"
var startOfWord = advance(text.startIndex, 5)
var endOfWord = advance(startOfWord, 5)
var range = startOfWord..<endOfWord
Even if some more work is required to figure out how far to advance (if you are looking for just one known word you can directly get it with NSRangeFromString)

Related

Replacing two ranges in a String simultaneously

Say you have a string that looks likes this:
let myStr = "Hello, this is a test String"
And you have two Ranges,
let rangeOne = myStr.range(of: "Hello") //lowerBound: 0, upperBound: 4
let rangeTwo = myStr.range(of: "this") //lowerBound: 7, upperBound: 10
Now you wish to replace those ranges of myStr with new characters, that may not be the same length as their original, you end up with this:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
myStr.replaceSubrange(rangeOne, with: "Bonjour") //Bonjour, this is a test String
myStr.replaceSubrange(rangeTwo, with: "ce") //Bonjourceis is a test String
Because rangeTwo is based on the pre-altered String, it fails to properly replace it.
I could store the length of the replacement and use it to reconstruct a new range, but there is no guarantee that rangeOne will be the first to be replaced, nor that rangeOne will actually be first in the string.
The solution is the same as removing multiple items from an array by index in a loop.
Do it backwards
First replace rangeTwo then rangeOne
myStr.replaceSubrange(rangeTwo, with: "ce")
myStr.replaceSubrange(rangeOne, with: "Bonjour")
An alternative could be also replacingOccurrences(of:with:)
This problem can be solved by shifting the second range based on the length of first the replaced string.
Using your code, here is how you would do it:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
let shift = "Bonjour".count - "Hello".count
let shiftedTwo = myStr.index(rangeTwo.lowerBound, offsetBy: shift)..<myStr.index(rangeTwo.upperBound, offsetBy: shift)
myStr.replaceSubrange(rangeOne, with: "Bonjour") // Bonjour, this is a test String
myStr.replaceSubrange(shiftedTwo, with: "ce") // Bonjour, ce is a test String
You can sort the range in descending order, then replace backwards, from the end to the start. So that any subsequent replacement will not be affect by the previous replacements. Also, it is safer to use replacingCharacters instead of replaceSubrange in case when dealing with multi-codepoints characters.
let myStr = "Hello, this is a test String"
var ranges = [myStr.range(of: "Hello")!,myStr.range(of: "this")!]
ranges.shuffle()
ranges.sort(by: {$1.lowerBound < $0.lowerBound}) //Sort in reverse order
let newWords : [String] = ["Bonjour😀","ce"].reversed()
var newStr = myStr
for i in 0..<ranges.count
{
let range = ranges[i]
//check overlap
if(ranges.contains(where: {$0.overlaps(range)}))
{
//Some range over lap
throw ...
}
let newWord = newWords[i]
newStr = newStr.replacingCharacters(in: range, with: newWord)
}
print(newStr)
My solution ended up being to take the ranges and replacement strings, work backwards and replace
extension String {
func replacingRanges(_ ranges: [NSRange], with insertions: [String]) -> String {
var copy = self
copy.replaceRanges(ranges, with: insertions)
return copy
}
mutating func replaceRanges(_ ranges: [NSRange], with insertions: [String]) {
var pairs = Array(zip(ranges, insertions))
pairs.sort(by: { $0.0.upperBound > $1.0.upperBound })
for (range, replacementText) in pairs {
guard let textRange = Range(range, in: self) else { continue }
replaceSubrange(textRange, with: replacementText)
}
}
}
Which works out to be useable like this
var myStr = "Hello, this is a test."
let rangeOne = NSRange(location: 0, length: 5) // “Hello”
let rangeTwo = NSRange(location: 7, length: 4) // “this”
myStr.replaceRanges([rangeOne, rangeTwo], with: ["Bonjour", "ce"])
print(myStr) // Bonjour, ce is a test.

How to split a NSAttributedString

I imported a NSAttributedString from a rtf-file and now I want to split it at another given String. With the attributedSubstring method you get one attributedSubstring as result, but I want to split it at every part, where the other String appeares, so the result should be an Array of NSAttributedStrings.
Example:
var source = NSAttributedString(string: "I*** code*** with*** swift")
var splitter = "***"
var array = //The method I am looking for
The result should be the following Array(with attributedStrings): [I, code, with, swift]
Following extension method maps the string components using Array.map into [NSAttributedString]
extension NSAttributedString {
func components(separatedBy string: String) -> [NSAttributedString] {
var pos = 0
return self.string.components(separatedBy: string).map {
let range = NSRange(location: pos, length: $0.count)
pos += range.length + string.count
return self.attributedSubstring(from: range)
}
}
}
Usage
let array = NSAttributedString(string: "I*** code*** with*** swift").components(separatedBy: "***")

How to remove certain characters in a string?

The string value varies sometimes it's
93.93% - 94.13, 85.34, %74.90, 88.21%
I just need to extract the double value like this.
93.93, 85.34, 74.90, 88.21
You can use regex to extract numbers from your string like this:
let sourceString = "93.93% - 94.13, 85.34, %74.90, 88.21%"
func getNumbers(from string : String) -> [String] {
let pattern = "((\\+|-)?([0-9]+)(\\.[0-9]+)?)|((\\+|-)?\\.?[0-9]+)" // Change this according to your requirement
let regex = try! NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: string, range: NSRange(string.startIndex..., in: string))
let result = matches.map { (match) -> String in
let range = Range(match.range, in: string)!
return String(string[range])
}
return result
}
let numberArray = getNumbers(from: sourceString)
print(numberArray)
Result:
["93.93", "94.13", "85.34", "74.90", "88.21"]
you should try using a regex like this for example :
[0-9]{2}.[0-9]{2}
This regex find all string that match two numbers, then a dot and two numbers again.
for each value such as var str='%74.90'; use this line -
var double=str.match(/[+-]?\d+(\.\d+)?/g).map(function(v) { return parseFloat(v); })[0];
Use Scanner to scan the values. Scanner is highly configurable and designed for scanning string and numeric values from loosely demarcated strings. Below is the example:
let characterSet = CharacterSet.init(charactersIn: "0123456789.").inverted
let scanner = Scanner(string: "93.93% - 94.13, 85.34, %74.90, 88.21%")
scanner.charactersToBeSkipped = characterSet
var numStr: NSString?
while scanner.scanUpToCharacters(from: characterSet, into: &numStr) {
print(numStr ?? "")
}
Output:
93.93
94.13
85.34
74.90
88.21
It is easier to understand comparatively regex.

How to get the first characters in a string? (Swift 3)

I want to get a substring out of a string which starts with either "<ONLINE>" or "<OFFLINE>" (which should become my substring). When I try to create a Range object, I can easily access the the first character by using startIndex but how do I get the index of the closing bracket of my substring which will be either the 8th or 9th character of the full string?
UPDATE:
A simple example:
let onlineString:String = "<ONLINE> Message with online tag!"
let substring:String = // Get the "<ONLINE> " part from my string?
let onlineStringWithoutTag:String = onlineString.replaceOccurances(of: substring, with: "")
// What I should get as the result: "Message with online tag!"
So basically, the question is: what do I do for substring?
let name = "Ajay"
// Use following line to extract first chracter(In String format)
print(name.characters.first?.description ?? "");
// Output : "A"
If you did not want to use range
let onlineString:String = "<ONLINE> Message with online tag!"
let substring:String = onlineString.components(separatedBy: " ")[0]
print(substring) // <ONLINE>
The correct way would be to use indexes as following:
let string = "123 456"
let firstCharIndex = string.index(string.startIndex, offsetBy: 1)
let firstChar = string.substring(to: firstCharIndex)
print(firstChar)
This Code provides you the first character of the string.
Swift provides this method which returns character? you have to wrap it before use
let str = "FirstCharacter"
print(str.first!)
Similar to OOPer's:
let string = "<ONLINE>"
let closingTag = CharacterSet(charactersIn: ">")
if let closingTagIndex = string.rangeOfCharacter(from: closingTag) {
let mySubstring = string.substring(with: string.startIndex..<closingTagIndex.upperBound)
}
Or with regex:
let string = "<ONLINE>jhkjhkh>"
if let range = string.range(of: "<[A-Z]+>", options: .regularExpression) {
let mySubstring = string.substring(with: range)
}
This code be some help for your purpose:
let myString = "<ONLINE>abc"
if let rangeOfClosingAngleBracket = myString.range(of: ">") {
let substring = myString.substring(to: rangeOfClosingAngleBracket.upperBound)
print(substring) //-><ONLINE>
}
Swift 4
let firstCharIndex = oneGivenName.index(oneGivenName.startIndex, offsetBy: 1)
let firstChar = String(oneGivenName[..<firstCharIndex])
let character = MyString.first
it's an simple way to get first character from string in swift.
In swift 5
let someString = "Stackoverflow"
let firstChar = someString.first?.description ?? ""
print(firstChar)
Swift 5 extension
extension String {
var firstCharactor: String? {
guard self.count > 0 else {
return nil
}
return String(self.prefix(1))
}
}

How I can take the number after the dot via substring?

I'm getting a value like this 264.8 and I want to take the value before and after the dot. I can take the value before the dot like this
var string = "264.8"
var index = string2.rangeOfString(".", options: .BackwardsSearch)?.startIndex
var substring = string.substringToIndex(index2!)
but please how I can take it after the dot?
Try this code:
var string = "264.8"
var numbers = string.componentsSeparatedByString(".")
print(numbers[0])
print(numbers[1])
var string = "264.8"
let partsArr = string.componentsSeparatedByString(".")
var beforeDot: String = partsArr[0]
var afterDot: String? = partsArr[1]
Just for the sake of completeness, an alternative is to use split:
let string = "264.8"
let result = string.characters.split(".").map { String($0) }
print(result[0]) // "264"
print(result[1]) // "8"
And another one is to use componentsSeparatedByCharactersInSet:
let string = "264.8"
let result = string.componentsSeparatedByCharactersInSet(NSCharacterSet.punctuationCharacterSet())
print(result[0]) // "264"
print(result[1]) // "8"
Alternatively, define a closure variable that handles the conversion for you
let mySubstringClosure : (String) -> (String) = { $0.componentsSeparatedByString(".").first ?? $0 }
let substring1 = mySubstringClosure("264.8") // "264"
let substring2 = mySubstringClosure("264") // "264"
let substring3 = mySubstringClosure("") // ""
Note that this code runs safely even if no dot . exists in the string, or of the string is empty.