从tap中获取UILabel中的角色索引

时间:2015-06-23 16:22:23

标签: ios uilabel nslayoutmanager nstextstorage nstextcontainer

我想获取UILabel上点击字符的索引。我已经将UILabel子类化了。在我的awakeFromNib()中,我有这个:

    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)

我正在按照标签的前5个字符的方式工作,就像我点击第一个字符一样,在我的touchesEnded中我得到一个0的索引:

var touchedCharacterIndex = layoutManager.characterIndexForPoint(touch.locationInView(self), inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

但是当我点击UILabel的前四个字符时,我得到4或0,这是不正确的。

谢谢,

更新

根据评论中的建议,我已经添加了这个:

 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
}

我在layoutSubviews()awakFromNib以及boundsframeattributedTexttext的制定者中将其称为。<​​/ p>

它现在给了我一些奇怪的索引,比如文本长度是21,点击第一个字母给我6。

3 个答案:

答案 0 :(得分:6)

TLDR:你得到了一些奇怪的索引,因为layoutManager看到的字符串与你在屏幕上看到的字符串的布局不同。

你得到了一些奇怪的索引:

- (NSUInteger)characterIndexForPoint:(CGPoint)point
    inTextContainer:(NSTextContainer *)container
    fractionOfDistanceBetweenInsertionPoints:(nullable CGFloat *)partialFraction;

因为您自己的layoutManager必须与系统layoutManager中的内部UILabel同步。这意味着当您在标签上设置字体,大小等时,内部layoutManager知道这一点,因为它由UILabel处理,但您的layoutManager实例却没有。

因此,layoutManager的文本布局“ see ”与屏幕上标签所呈现的实际文本位于不同的位置(我打赌的默认字体和大小)。

复杂的部分是保持这些属性同步,你可以做的就是在你的属性文本中设置这些属性,如:

[mutableAttributedString addAttribute:NSFontAttributeName 
                                value:self.font 
                                range:NSMakeRange(0, mutableAttributedString.string.length)];

将此属性字符串传递给您的NSTextStorage实例。这为我解决了所有问题。

信息:为了帮助您进行调试,您可以使用以下内容通过layoutManager呈现字符串“ see ”:

- (void)drawTextInRect:(CGRect)rect
{
    [super drawTextInRect:rect];
    [self.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, self.textStorage.length) atPoint:CGPointMake(0, 0)];
}

如果你看到2个字符串叠加在一起并带有一些故障,那么这就是你的问题。如果你只能看到一个应该是因为它们完全一致并且你很好!

答案 1 :(得分:2)

我一直在努力找到适用于此的东西。这是我能够上班的。它从其他一些例子中提取,但我必须包括段落样式才能使它完全排列。希望这可以帮助别人。

NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
NSTextStorage *textStorage;

NSMutableParagraphStyle *paragraphStyle = NSMutableParagraphStyle.new;
paragraphStyle.alignment = self.label.textAlignment;
// Configure textContainer
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = self.label.lineBreakMode;
textContainer.maximumNumberOfLines = self.label.numberOfLines;

CGPoint locationOfTouchInLabel = [tapGesture locationInView:tapGesture.view];
CGSize labelSize = tapGesture.view.bounds.size;
textContainer.size = self.label.bounds.size;

NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.noPoliciesDescriptionLabel.attributedText];
[attributedText addAttributes:@{NSFontAttributeName: self.label.font, NSParagraphStyleAttributeName: paragraphStyle} range:NSMakeRange(0,self.label.attributedText.string.length)];
textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];

// Configure layoutManager and textStorage
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];

CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer];
CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                          (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);

CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                     locationOfTouchInLabel.y - textContainerOffset.y);

NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer
                                                        inTextContainer:textContainer
                               fractionOfDistanceBetweenInsertionPoints:nil];

NSRange range = NSMakeRange(indexOfCharacter, 1);
NSString *value = [self.label.text substringWithRange:range];
NSLog(@"%@, %zd, %zd" , value, range.location, range.length);

答案 2 :(得分:1)

我认为问题源于NSTextContainer将文本设置为UITextView,这意味着它不会像UILabel那样垂直居中。

如果您运行以下代码,您可以看到字形布局的位置。

var location = layoutManager.locationForGlyphAtIndex(0);

它应该显示字形被布置在textContainer的顶部。

所以无论你在哪里打电话

var point = tapGesture.locationInView(self);
需要调整

以考虑UILabel的垂直位移。