Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

layoutManager boundingRectForGlyphRange:inTextContainer: does not work for all strings

I have a UILabel with a tweet like string, including mentions of other users.

Hey @stephen and @frank and @Jason1.

I am attempting to have each mention be tappable so I can load that user's profile. I found some code from another SO post (How do I locate the CGRect for a substring of text in a UILabel?) that I was able to use in order to locate the position of each mention in the string. However it usually doesn't work for the last or last 2 mentions in a post.

The method from the SO post (slightly modified):

- (CGRect)boundingRectForCharacterRange:(NSRange)range
{
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.myLabel.attributedText];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.myLabel.bounds.size];
    textContainer.lineFragmentPadding = 0;
    [layoutManager addTextContainer:textContainer];

    NSRange glyphRange;

    // Convert the range for glyphs.
    [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];

    return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}

Then, in touchesEnded:, I loop over each mention, get the range in the main string, and check if the touch is inside that CGRect.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{        
    UITouch *touch = touches.allObjects[0];

    for (NSString *mention in self.mentions) {
        NSRange range = [self.postText rangeOfString:mention options:NSCaseInsensitiveSearch];
        CGRect rect = [self boundingRectForCharacterRange:range];
        NSLog(@"rect for %@ is %@", mention, NSStringFromCGRect(rect));
    }
}

// Output from above
rect for @stephen is {{33.388, 0}, {72.471001, 20.553001}}
rect for @frank is {{143.021, 0}, {49.809998, 20.553001}}
rect for @Jason1 is {{0, 0}, {0, 0}}

And this works great most of the time, however @Jason1 does not get matched. I've switched the order of the names and it's always the last one. My label does wrap, but it still sometimes matches names on the 2nd and 3rd lines. Is there a setting or something I'm missing? I've tried changing the size and font of the labels but no luck. I'm at a real loss here.

like image 829
Stephen Avatar asked Jul 26 '14 11:07

Stephen


1 Answers

The fix for this is not to set the height constraint when initializing the NSTextContainer. Use a very large number instead.

NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(self.myLabel.bounds.size.width, CGFLOAT_MAX)];
like image 102
William Chen Avatar answered Nov 08 '22 00:11

William Chen