O Fenômeno
O Fenômeno

Reputation: 455

NSMutableAttributedString Fatal Exception: NSRangeException

When I try to apply attributes to the sub-string, I sometimes get a crash in following code:

// Dummy strings
let originalString = "Some example string"
let searchSubString = "exam"

// Get range of sub-string for which new attributes are to be set.
let rangeOfSubString: NSRange = (originalString.lowercaseString as NSString).rangeOfString(searchSubString.lowercaseString)

// Apply new attributes to the sub-string in original string and show it in UILabel.
let attributedOriginalString = NSMutableAttributedString(string: originalString, attributes: [NSForegroundColorAttributeName : UIColor.blueColor()])
attributedOriginalString.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(14.0), range: rangeOfSubString)
attributedOriginalString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: rangeOfSubString)
self.textLabel.attributedText = attributedOriginalString

And following is the stack trace:

Thread : Fatal Exception: NSRangeException
0 CoreFoundation 0x26cbefef __exceptionPreprocess
1 libobjc.A.dylib 0x35362c8b objc_exception_throw
2 CoreFoundation 0x26cbef35 -[NSException initWithCoder:]
3 Foundation 0x2793ac3b -[NSRLEArray objectAtIndex:effectiveRange:]
4 Foundation 0x27954b2d -[NSConcreteMutableAttributedString addAttribute:value:range:]

I am not able to repro it though, but I got this crash log through crashlytics.

The crash log seems to say that the rangeOfSubString is beyond the bounds of originalString, but I don't think it will ever happen.

Can anyone point me what could be the reason of the crash?

Upvotes: 0

Views: 613

Answers (1)

O Fenômeno
O Fenômeno

Reputation: 455

Posting my answer in case if others face such kind of issue:

Replace

let rangeOfSubString: NSRange = (originalString.lowercaseString as NSString).rangeOfString(searchSubString.lowercaseString)

with

let rangeOfSubString: NSRange = (originalString as NSString).rangeOfString(searchSubString, options: .CaseInsensitiveSearch)

First way of calculating sub-string range is wrong cause:

  • It is calculating sub-string range WRT lowercase versions and applying attributes for that range to the original-string(non lowercase). But the range calculated using above method may go beyond original-string range in case of special characters, cause lowercase character may require more length than uppercase versions and vice versa.

    i.e Length of uppercase Turkish character "İ" is 1, while for its lowercase version ”i̇" it is 2.

  • For ex. If we search for "İ" in "Hİ"(length = 2), the 1st method will give you a range(1, 2) cause "İ" in lowercase requires length = 2, but if you apply range(1, 2) to "Hİ", it will go beyond its range which is range(0, 2) or if there are more characters in original string like "Hİabc", it will correspond to "İa" which is again wrong.

  • So calculate range WRT original string as later I need to apply that range to original-string itself.

Also see apple's documentation:

lowercaseString:

Case transformations aren’t guaranteed to be symmetrical or to produce strings of the same lengths as the originals.

lcString = [myString lowercaseString];

might not be equal to this statement:

lcString = [[myString uppercaseString] lowercaseString];

For example, the uppercase form of “ß” in German is “SS”, so converting “Straße” to uppercase, then lowercase, produces this sequence of strings:

“Straße”

“STRASSE”

“strasse”

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/#//apple_ref/occ/instp/NSString/lowercaseString

Upvotes: 1

Related Questions