layoutManager boundingRectForGlyphRange:inTextContainer: does not work for all strings
Asked Answered
K

1

8

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.

Knutson answered 26/7, 2014 at 11:14 Comment(1)
Did you get it solved? Please share solution if you got any.Ambience
E
13

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)];
Eyde answered 8/10, 2014 at 16:20 Comment(3)
On iOS7, you should use this method. But on iOS8 and above, you can use label.size happily.Need
FWIW, I just fixed a bug like this by setting the height to max on iOS 13.5.Manners
OMG, works like magic! YOU SAVED MY DAY!!Mikesell

© 2022 - 2024 — McMap. All rights reserved.