NSRange from Swift Range? NSRange from Swift Range? ios ios

NSRange from Swift Range?


Swift String ranges and NSString ranges are not "compatible".For example, an emoji like πŸ˜„ counts as one Swift character, but as two NSStringcharacters (a so-called UTF-16 surrogate pair).

Therefore your suggested solution will produce unexpected results if the stringcontains such characters. Example:

let text = "πŸ˜„πŸ˜„πŸ˜„Long paragraph saying!"let textRange = text.startIndex..<text.endIndexlet attributedString = NSMutableAttributedString(string: text)text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in    let start = distance(text.startIndex, substringRange.startIndex)    let length = distance(substringRange.startIndex, substringRange.endIndex)    let range = NSMakeRange(start, length)    if (substring == "saying") {        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)    }})println(attributedString)

Output:

πŸ˜„πŸ˜„πŸ˜„Long paragra{}ph say{    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";}ing!{}

As you see, "ph say" has been marked with the attribute, not "saying".

Since NS(Mutable)AttributedString ultimately requires an NSString and an NSRange, it is actuallybetter to convert the given string to NSString first. Then the substringRangeis an NSRange and you don't have to convert the ranges anymore:

let text = "πŸ˜„πŸ˜„πŸ˜„Long paragraph saying!"let nsText = text as NSStringlet textRange = NSMakeRange(0, nsText.length)let attributedString = NSMutableAttributedString(string: nsText)nsText.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in    if (substring == "saying") {        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)    }})println(attributedString)

Output:

πŸ˜„πŸ˜„πŸ˜„Long paragraph {}saying{    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";}!{}

Update for Swift 2:

let text = "πŸ˜„πŸ˜„πŸ˜„Long paragraph saying!"let nsText = text as NSStringlet textRange = NSMakeRange(0, nsText.length)let attributedString = NSMutableAttributedString(string: text)nsText.enumerateSubstringsInRange(textRange, options: .ByWords, usingBlock: {    (substring, substringRange, _, _) in    if (substring == "saying") {        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)    }})print(attributedString)

Update for Swift 3:

let text = "πŸ˜„πŸ˜„πŸ˜„Long paragraph saying!"let nsText = text as NSStringlet textRange = NSMakeRange(0, nsText.length)let attributedString = NSMutableAttributedString(string: text)nsText.enumerateSubstrings(in: textRange, options: .byWords, using: {    (substring, substringRange, _, _) in    if (substring == "saying") {        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.red, range: substringRange)    }})print(attributedString)

Update for Swift 4:

As of Swift 4 (Xcode 9), the Swift standard libraryprovides method to convert between Range<String.Index> and NSRange.Converting to NSString is no longer necessary:

let text = "πŸ˜„πŸ˜„πŸ˜„Long paragraph saying!"let attributedString = NSMutableAttributedString(string: text)text.enumerateSubstrings(in: text.startIndex..<text.endIndex, options: .byWords) {    (substring, substringRange, _, _) in    if substring == "saying" {        attributedString.addAttribute(.foregroundColor, value: NSColor.red,                                      range: NSRange(substringRange, in: text))    }}print(attributedString)

Here substringRange is a Range<String.Index>, and that is converted to thecorresponding NSRange with

NSRange(substringRange, in: text)


For cases like the one you described, I found this to work. It's relatively short and sweet:

 let attributedString = NSMutableAttributedString(string: "follow the yellow brick road") //can essentially come from a textField.text as well (will need to unwrap though) let text = "follow the yellow brick road" let str = NSString(string: text)  let theRange = str.rangeOfString("yellow") attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.yellowColor(), range: theRange)


The answers are fine, but with Swift 4 you could simplify your code a bit:

let text = "Test string"let substring = "string"let substringRange = text.range(of: substring)!let nsRange = NSRange(substringRange, in: text)

Be cautious, as the result of range function has to be unwrapped.