简体   繁体   中英

NSLayoutManager, boundingRectForGlyphRange and emoji

I am displaying text that may contain emoji using NSAttributedString 's drawInRect(rect: CGRect) method. Since I want to detect taps on the text I use the following method to see which character has been tapped:

let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
layoutManager.usesFontLeading = true
textStorage.addLayoutManager(layoutManager)

let textContainer = NSTextContainer(size: containerSize)
layoutManager.addTextContainer(textContainer)

textContainer.lineFragmentPadding = 0.0

layoutManager.ensureLayoutForTextContainer(textContainer)

let tappedIndex = layoutManager.characterIndexForPoint(point, 
        inTextContainer: textContainer, 
        fractionOfDistanceBetweenInsertionPoints: nil)

This gives the correct index that I can work with until I start adding emoji to the text. As soon as emoji are added there starts to be an offset for the detection. This led me to look at the bounding rectangles of glyphs that I was looking for. I noticed that the bounding rectangles of emoji were too large. I set up the following test case to check the difference:

let emojiText = "😀"
let font = UIFont.systemFontOfSize(20.0)
let containerSize = CGSize(width: 300.0, height: 20000.0)

let attributedString = NSAttributedString(string: emojiText, attributes: [NSFontAttributeName: font])

let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
layoutManager.usesFontLeading = true
textStorage.addLayoutManager(layoutManager)

let textContainer = NSTextContainer(size: containerSize)
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0.0

layoutManager.ensureLayoutForTextContainer(textContainer)

let glyphRect = layoutManager.boundingRectForGlyphRange(NSRange(location: 0, length: attributedString.length), inTextContainer: textContainer)
let boundingRect = attributedString.boundingRectWithSize(containerSize, options:[.UsesLineFragmentOrigin, .UsesFontLeading], context: nil)

Executing this code resulted the following CGRect s:

glyphRect = (0.0, 0.0, 23.0, 28.875)
boundingRect = (0.0, 0.0, 23.0, 23.8671875)

What this means is that these two methods give two entirely different sizes! This wouldn't be a problem, but the 'offset' stacks with more lines.

Example of the stacked offset

I set a purple background for the character the characterIndexForPoint gave me, gave the rect of boundingRectForGlyphRange a green outline and the yellow dot is the actual taplocation. Note that the green rectangle lines up nicely with a different character, however, this is no indication whatsoever, since it just happens to line up nicely in this specific case.

Am I overlooking something obvious or is this an issue in iOS?

I have solved the issue. It appears that NSAttributedString.drawInRect draws differently from CoreText . I now use the following code to draw the text in drawRect :

let totalRange = layoutManager.glyphRangeForTextContainer(textContainer)

layoutManager.drawBackgroundForGlyphRange(range, atPoint: CGPointZero)
layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPointZero)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM