3

When I'm trying to add attributes to attributed string that contains emojis sometimes some of emojis are broken. For regular text works perfectly. Any ideas what am I doing wrong?

Here is my function - it should bold quotations (text between first and last quotation mark)

func boldQuotation(str: String, fontSize: CGFloat) -> NSAttributedString {
    let normalAttributes = [NSFontAttributeName : UIFont.systemFontOfSize(fontSize)]
    let boldAttributes = [NSFontAttributeName : UIFont.boldSystemFontOfSize(fontSize)]
    let attributedStr = NSMutableAttributedString(string: str, attributes: normalAttributes)

    let firstQuotationMarkRange = str.rangeOfString("\"")
    let lastQuotationMarkRange  = str.rangeOfString("\"", options: [.BackwardsSearch], range: nil, locale: nil)
    guard let firstIndex = firstQuotationMarkRange?.startIndex, lastIndex = lastQuotationMarkRange?.endIndex else {
        return attributedStr
    }
    attributedStr.addAttributes(boldAttributes, range: NSMakeRange(str.startIndex.distanceTo(firstIndex), firstIndex.distanceTo(lastIndex)))
    return attributedStr
}

Here is sample of text that cannot be correctly attributed:

let str = "\"\""

The result looks like this:

enter image description here

I'm using Swift 2.3, iOS 10.2.1 , Xcode 8.2.1, Deployment target: 9.3

ukaszm
  • 254
  • 2
  • 9

1 Answers1

0

Ok, after further investigation (and no answer) I finally found reason and solution of that bug.

There was a problem with creation NSRange using indices of Range (obtained from String). Long story short - Swift String ranges and NSString ranges shouldn't be used to converting one to another. At least not like this:

let firstQuotationMarkRange     = str.rangeOfString("\"")
let lastQuotationMarkRange      = str.rangeOfString("\"", options: [.BackwardsSearch], range: nil, locale: nil)
guard let firstIndex = firstQuotationMarkRange?.startIndex, lastIndex = lastQuotationMarkRange?.endIndex else {
        return attributedStr
    }
//THIS IS WRONG:
range = NSMakeRange(str.startIndex.distanceTo(firstIndex), firstIndex.distanceTo(lastIndex))

So what's the correct solution? Convert String to NSString first and then start looking for substring range. Here is fixed function:

func boldQuotation(str: String, fontSize: CGFloat) -> NSAttributedString {
    let normalAttributes = [NSFontAttributeName : UIFont.systemFontOfSize(fontSize)]
    let boldAttributes = [NSFontAttributeName : UIFont.boldSystemFontOfSize(fontSize)]
    let attributedStr = NSMutableAttributedString(string: str, attributes: normalAttributes)

    let nsStr = str as NSString
    let firstQuotationMarkRange = nsStr.rangeOfString("\"")
    let lastQuotationMarkRange  = nsStr.rangeOfString("\"", options: [.BackwardsSearch])
    guard firstQuotationMarkRange.length > 0 && lastQuotationMarkRange.length > 0 else {
        return attributedStr
    }
    let nsrange = NSMakeRange(firstQuotationMarkRange.location, lastQuotationMarkRange.location + lastQuotationMarkRange.length - firstQuotationMarkRange.location)
    attributedStr.addAttributes(boldAttributes, range: nsrange)
    return attributedStr
}

Solved thanks to this post: NSRange from Swift Range

Community
  • 1
  • 1
ukaszm
  • 254
  • 2
  • 9