A curious case of NSAttributedString

Foreword

If you encounter problems of properly setting/adding attributes to NSAttributedString while the string is composed of multibyte character sets, or MBCS for short, then this article is for you.

What’s the problem?

This case happened when I was reviewing a pull request issued by my colleague. The problem was that a word in a sentence could not be properly shown with font weight .medium.

The sentence was composed of Traditional Chinese characters. To fulfill certain UI requirements UITextView was used instead of UILabel. func addAttributes([NSAttributedString.Key: Any], range: NSRange) was used to add attributes to an NSMutableAttributedString and set to the property attributedString of a UITextView instance. The goal is to show a message, such as “This is a sentence.” with one or more words bold. For instance, “This is a sentence.”

The same way works onUILabel along with its attributedString property. However, in the case of UITextView and Traditional Chinese just didn’t work as expected. Suppose the message is: “這是一個『測試字串』”, which means “This is a ‘string for testing’”, and I want to show it as “這是一個『測試字串』”. What came out on UI was “這是一個『測試字串』”. The same attributed string assigned to an UILabel instance, then what came out was “這是一個『測試字串』” — That’s nonsense!

An instance of NSMutableAttributedString assigned and rendered by a UILabel and UITextView. The upper one is UILabel and it works as expected. However, The lower one didn’t present medium font weight.

By printing the attributed string, the ranges of the attributes were correct:

mutableString: 這是一個『{NSColor = "UIExtendedGrayColorSpace 0 1";NSFont = "<UICTFont: 0x7ffe388043e0> font-family: \".SFUIText\"; font-weight: normal; font-style: normal; font-size: 15.00pt";}測試字串{NSFont = "<UICTFont: 0x7ffe38808080> font-family: \".SFUIText-Medium\"; font-weight: normal; font-style: normal; font-size: 15.00pt";}』。{NSColor = "UIExtendedGrayColorSpace 0 1";NSFont = "<UICTFont: 0x7ffe388043e0> font-family: \".SFUIText\"; font-weight: normal; font-style: normal; font-size: 15.00pt";}

That’s weird.

I also tried to add an attribute to the sentence with a given NSRange through NSMakeRange(0, 1), something weird happened — That’s nonsense too!

UILabel has set the first character to the medium font weight, yet UITextView has set the entire sentence to medium font weight.

So, what would happen to English sentences? It seems that both UILabel and UITextView are working fine.

Both UILabel and UITextView have presented .medium font weight to the word “sentence”.

What!?

Okay? Then what’s next?

Then we started to try to work around this.

Firstly, I asked my colleague to try to set the font explicitly to a font what support Traditional Chinese. PingFangTC-Regular and PingFangTC-Medium were given as the font for the sentence and to the word we wanted to emphasize. Although this worked, yet we could not align the font used in the same UI when the system language is not Traditional Chinese.

Next, I tried to add space before and after the symbol 『 and 』. As a result, it worked fine:

The font weight is now okay, leaving one space prior and after the symbol 『 and 』.

I was not satisfied with the outcome, and I tried another way by changing the content to這是一個『\0測試字串\0 to avoid the leading and trailing spaces. Surprisingly, it worked as well:

Both of the UILabel instance and the UITextView instance presented the font weight correctly.

Okay, we’ve got workarounds for the problem. Finally, my colleague decided to adopt the “adding-space” one, considering that our Project manager is the one who provides localized strings. Adding \0 might be confusing.

I put an archived Xcode playground to my Google drive to elucidate this problem, you may download it through the link: https://drive.google.com/open?id=1F4lsmR-yHWE4Sj7QVczStaIgvOJQuV3D

Conclusion

In sum, it seems that UITextView’s attributedis buggy while working with MBCS contents. As my colleagues are going to WWDC, we are going to clarify this issue during the Lab session. Before being clarified, we would continue our work with the workarounds we’ve found. If you are getting into the same problem, I hope my experience may give you little help. Cheers!

--

--