5

我想获取 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

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

4

3 回答 3

11

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


您从以下位置获得了一些奇怪的索引:

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

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

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

复杂的部分是使这些属性保持同步,您可以在属性文本上设置这些属性,例如:

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

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


信息:为了帮助您进行调试,您可以使用以下方法渲染字符串“ seenlayoutManager

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

如果您看到 2 根弦相互重叠并出现一些故障,这就是您的问题所在。如果您只能看到一个应该是的,因为它们完全对齐并且您很好!

于 2015-12-12T09:37:53.797 回答
2

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

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

var location = layoutManager.locationForGlyphAtIndex(0);

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

所以无论你打电话到哪里

var point = tapGesture.locationInView(self);

将需要调整以考虑 UILabel 的垂直偏移。

于 2015-06-24T16:02:43.317 回答
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);
于 2017-11-17T19:49:47.703 回答