I'd like to get the index for the tapped character on a UILabel. I've subclassed a UILabel. In my awakeFromNib() I have this:
layoutManager = NSLayoutManager()
textStorage = NSTextStorage(attributedString: self.attributedText)
textStorage.addLayoutManager(layoutManager)
textContainer = NSTextContainer(size: CGSizeMake(self.frame.size.width, self.frame.size.height))
textContainer.lineFragmentPadding = 0
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.lineBreakMode = self.lineBreakMode
textContainer.size = self.frame.size
layoutManager.addTextContainer(textContainer)
It is working how I want it to for the first 5 characters of the label, as in I tap the first character and in my touchesEnded I get an index of 0:
var touchedCharacterIndex = layoutManager.characterIndexForPoint(touch.locationInView(self), inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
But when I tap anywhere past the first four characters of the UILabel, I get either 4 or 0, which is incorrect.
Thanks,
Update
From a suggestion in the comments, I've added this:
func updateTextStorage() {
if let attributedText = self.attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
}
textStorage?.addLayoutManager(layoutManager)
textContainer = NSTextContainer(size: CGSizeMake(self.frame.size.width, self.frame.size.height))
textContainer?.lineFragmentPadding = 7
textContainer?.maximumNumberOfLines = self.numberOfLines
textContainer?.lineBreakMode = self.lineBreakMode
textContainer?.size = self.bounds.size
layoutManager.addTextContainer(textContainer!)
layoutManager.textStorage = textStorage
}
I call this in layoutSubviews()
, awakFromNib
and the setters of bounds
, frame
, attributedText
and text
.
And it is now giving me some weird indexes, like if text length is 21, tapping the first letter gives me 6.
TLDR: You are getting some weird indexes because the string seen by your layoutManager
is layed-out differently than the one you actually see on screen.
You are getting some weird indexes from:
- (NSUInteger)characterIndexForPoint:(CGPoint)point
inTextContainer:(NSTextContainer *)container
fractionOfDistanceBetweenInsertionPoints:(nullable CGFloat *)partialFraction;
because your own layoutManager
have to be in sync with the internal layoutManager
from the system UILabel
. Which mean when you set a font, size, etc. on your label, the internal layoutManager
knows about that because it's taken care by UILabel
, but your layoutManager
instance does not.
So the text layout "seen" by your layoutManager is in a different position than the actual text rendered by the label on screen (default font and size I bet).
The complicated part is to keep those properties in sync, what you can do is set those on your attributed text like:
[mutableAttributedString addAttribute:NSFontAttributeName
value:self.font
range:NSMakeRange(0, mutableAttributedString.string.length)];
And pass this attributed string to your NSTextStorage
instance. This fixed all the issues for me.
INFO: To help you debug you can render the string "seen" by your layoutManager
using this:
- (void)drawTextInRect:(CGRect)rect
{
[super drawTextInRect:rect];
[self.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, self.textStorage.length) atPoint:CGPointMake(0, 0)];
}
If you see 2 strings layed-out on top of each other with some glitch this is your problem right there. If you can only see one that should be because they are perfectly aligned and your are good!
I think the issue is stemming from the fact that NSTextContainer lays out text like a UITextView, which means that it will not be vertically centered like a UILabel.
If you run the following piece of code you can see where your glyphs are laying out.
var location = layoutManager.locationForGlyphAtIndex(0);
It should show that the glyphs are being laid out at the top of your textContainer.
So wherever you call
var point = tapGesture.locationInView(self);
will need to be adjusted to take the vertical shift of the UILabel into account.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With