NSStribg and String compatibility

ffunc calculateRange(symbol: String, amountText: String) -> Range<String.Index>? {

return amountText.range(of: symbol)

}

func calculateNSRange(range: Range<String.Index>, in string: String) -> NSRange {

return NSRange(range, in: string)

}

let dollar = “$”

let rupee = “₹”

let dollarAmount = “(dollar)2,345”

let rupeeAmount = “(rupee)2,345"

if let range = calculateRange(symbol: dollar, amountText: dollarAmount) {

let nsrange = calculateNSRange(range: range, in: dollarAmount)

print(“NSRange for $: (nsrange)“)

}

if let range = calculateRange(symbol: rupee, amountText: rupeeAmount) {

let nsrange = calculateNSRange(range: range, in: rupeeAmount)

print(“NSRange for ₹: (nsrange)“)

}

// Prints

//NSRange of $: {0, 1}

//NSRange of ₹: {0, 1}

if let range = calculateRange(symbol: dollar, amountText: dollarAmount) {

let dollarNSString = NSString(string: dollarAmount)

let nsrange = calculateNSRange(range: range, in: dollarNSString as String)

print(“@NSRange for $: (nsrange)“)

}

if let range = calculateRange(symbol: rupee, amountText: rupeeAmount) {

let rupeeNSString = NSString(string: rupeeAmount)

let nsrange = calculateNSRange(range: range, in: rupeeNSString as String)

print(“@NSRange for ₹: (nsrange)“)

}

// Prints

//@NSRange for $: {0, 1}

//@NSRange for ₹: {0, 3}

If you run above code in playground you’ll see when I use same string content as NSString I get the different range. What I’m doing is calculating Range from String then converting that Range into NSRange using NSString that has the same content. So my question is why NSRange(range, in: string) method not calculating right range.

As far as I understood that NSString storage is different than String, but they are also easily bridged to each other. Can anyone explains me if they are easily bridged then why do we have different behavior of NSRange(range, in: string) ?

Change the line:

let nsrange = calculateNSRange(range: range, in: rupeeNSString as String)

to this:

let nsrange = calculateNSRange(range: range, in: rupeeAmount)

The basic rule is this: don't use the indices on a string after mutating that string. Converting to NSString and back counts as a mutation. So in this case need to keep the original string around.

Also note that you can avoid converting a range to NSRange by looking for the currency symbol using the equivalent NSString method:

let nsrange = rupeeNSString.range(of: "₹")
2 Likes

Also, NSRange represents a range in UTF-16 while Range<String.Index> represents a range in the backing representation of String, which is UTF-8 for native Swift strings but UTF-16 when bridged from NSString.

What's likely happening is that when you convert the to NSRange by specifying the bridged string instance, it thinks the indices are already UTF-16 so they're left as is, whereas when you're using the correct string they're converted from UTF-8 indices to UTF-16 ones that are suitable for NSRange. Since is represented as three UTF-8 code units (E2 82 B9), you see a length of 3, while in UTF-16 it is only one code unit (20B9).

1 Like

Thanks Michel for the explanation