简体   繁体   中英

LayoutManager boundingRectForGlyphRange for multiline texts

I have a text, which is displayed on a screen exactly like that:

#first SomePersonFirstName
SomePersonLastName #secondTag

In my application I keep information about 3 tags for this text together with their range, which are:

  1. #first
  2. SomePersonFirstName SomePersonLastName
  3. #secondTag

Now after I display it to user I want to detect which one was clicked, so I iterate through all informations I have, get a boundingRectForGlyphRange for each of them, and check if this rect contains a point where it was clicked.

Everything works in general, but I got a strange situation which breaks my idea about how I do it. Problem is that bounding rects overlaps for those tags.

在此处输入图片说明

Because person tag was splitted into 2 lines, bound rects starts at 0,0 and covers #first bounding rect.

Problem is that when you click on this conflicted areas, there is no way to figure out which one was clicked.

I actually have no idea how to approach that. Is there any other way to detect bounding rect but that will not actually bound to empty areas, that will cover just text itself ?

EDIT

The code I'm using:

- (CGRect)boundingRectForCharacterRange:(NSRange)range
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.labelTitle.attributedText];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(self.labelTitle.bounds.size.width, CGFLOAT_MAX)];
    textContainer.lineFragmentPadding = 0;
    textContainer.lineBreakMode = self.labelTitle.lineBreakMode;
    [layoutManager addTextContainer:textContainer];

    NSRange glyphRange;
    [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
    return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}

-(void)tapped:(UIGestureRecognizer *)recognizer
{
    UILabel *label = (UILabel *)recognizer.view;
    CGPoint location = [recognizer locationInView:label];

    CommentTag* selectedCommentTag = nil;
    for (CommentTag* commentTag in self.comment.tags)
    {
        CGRect commentRect = [self boundingRectForCharacterRange:[commentTag tagRange]];
        if(CGRectContainsPoint(commentRect, location))
        {
            if(selectedCommentTag == nil)
            {
                selectedCommentTag = commentTag;
            }
            else
            {
                if(selectedCommentTag.offset > commentTag.offset)
                {
                    selectedCommentTag = commentTag;
                }
            }
        }
    }

    if(selectedCommentTag)
    {
        if([selectedCommentTag.type isEqualToString:COMMENTTAG_TYPE_TAG])
        {
            NSLog(@"%@", [selectedCommentTag value]);
        }
    }
}

Here I just take the first tag to the left, but that doesn't solve a problem of #secondTag from my example

There are two (three) instances when two bounding rects can overlap:

  1. The two ranges overlap index values.
  2. One of the bounding rects occupies multiple lines.
  3. Both of the bounding rects occupy multiple lines.

For (1), you're just going to have to make your own set of rules for when you accept a click on a common glyph.

For (2), always prefer touches on the range that spans only one line -- you can tell this by comparing the rect height.

For (3), you'll have to iterate through the lines.

Alternatively, have you considered the opposite method?

NSRange characterRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(point.x, point.y, 1, 1) inTextContainer:container];
if (NSLocationInRange(characterRange.location, firstRange) {
  // ...
} else if (NSLocationInRange(characterRange.location, secondRange) {
  // ...
} // etc...

By converting your touch point to a CGRect with size 1x1 , you're able to get your layoutManager to provide you an NSRange of length 1 representing the single glyph at your touch point. Using this NSRange 's location , you can compare to your NSRange list and see which one you hit. Using your source:

- (NSUInteger)glyphIndexForPoint:(CGPoint)point
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.labelTitle.attributedText];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(self.labelTitle.bounds.size.width, CGFLOAT_MAX)];
    textContainer.lineFragmentPadding = 0;
    textContainer.lineBreakMode = self.labelTitle.lineBreakMode;
    [layoutManager addTextContainer:textContainer];
    return [layoutManager glyphIndexForPoint:point inTextContainer:textContainer];;
}

-(void)tapped:(UIGestureRecognizer *)recognizer
{
    UILabel *label = (UILabel *)recognizer.view;
    CGPoint location = [recognizer locationInView:label];
    NSUInteger glyphIndex = [self glyphIndexForPoint:location];

    CommentTag* selectedCommentTag = nil;
    for (CommentTag* commentTag in self.comment.tags)
    {
        if (NSLocationInRange(glyphIndex, [commentTag tagRange]))
        {
            NSLog(@"%@", commentTag); //selected tag
            break
        }
    }
}

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