Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSMutableAttributedString will not display attribute that begins at nonzero index

UPDATE:

I created a really simple standalone project to demonstrate the bug. If anyone would like to pull same and see if they can spot where I've gone wrong, I'd sure appreciate it. There's not much code to look through. Public repo here: https://github.com/reidnez/NSAttributedStringBugDemo

I'm having a very strange issue here: I have a tableview. Each cell has a title label with 1-3 words, and a keywords label with several CSV keywords. I also have a search bar. The requirement is that as the user types into the search bar, any partial matches on both the title and keywords for each cell are shown highlighted. Screenshots:

Everything looks as it should searching on "Fa"

"an" highlights keyword, but not title

First image is A-Okay. In the second image, the "an" of the title label should be highlighted. But, as you can see, not so much...

This works perfectly fine on the "keywords" label, as you can see above. The attributed strings for both of these labels are created by a category I wrote (code below). The same method is called on both strings, and appears to behave the same from what the debugger is telling me. The UI tells a different story.

I have stepped through the debugger numerous times, and in all cases, the attributed string appears to have been configured correctly. I have also verified that something else is not calling [tableView reloadData] and that no other place in my code is overwriting the label's value. This is how matching on "an" for "Fang" looks in the debugger, just before the cell is returned at the end of cellForRowAtIndexPath:

(lldb) po customCell.entryTitleLabel.attributedText
F{
}an{
NSBackgroundColor = "UIDeviceRGBColorSpace 0.533333 0.835294 0.156863 1";
}g{
}

Looks good to me...that is exactly what I want. But when the cell renders, there are NO highlights to be seen! Weirder yet, as an experiment I tried setting the label to a completely arbitrary attributedString that I created right in cellForRow:

NSMutableAttributedString *fake = [[NSMutableAttributedString alloc] initWithString:@"Fang"];
            [fake addAttribute:NSBackgroundColorAttributeName value:MATCH_TEXT_HILIGHT_COLOR range:NSMakeRange(1, 2)];
            customCell.entryTitleLabel.attributedText = fake;

This, too fails. No highlighting at all...but I CAN highlight any substring in the range of {0, 1} to {0, fake.length} and it behaves as expected. Again, it seemingly refuses to highlight any substring that does not begin at index 0--but only for the title label.

Am I losing my mind? What am I missing?

Below is my category...but I am fairly confident the problem does not lie here, because it functions perfectly for the keywords string, and (again) the attributes appear to be set correctly just before the cell returns:

-(void)hilightMatchingSubstring:(NSString*)substring color:(UIColor*)hilightColor range:(NSRange)range
{
    if ([self.string compare:substring options:NSCaseInsensitiveSearch] == NSOrderedSame) {
        [self addAttribute:NSBackgroundColorAttributeName value:hilightColor range:NSMakeRange(0, self.length)];
        return;
    }

    // Sanity check. Make sure a valid range has been passed so that we don't get out-of-bounds crashes. Default to return self wrapped in an attributed string with no attributes.
    NSRange selfRange = NSMakeRange(0, self.length);
    if (NSIntersectionRange(selfRange, range).length == 0) {
        NSLog(@" \n\n\n*** Match range {%lu, %lu} does not intersect main string's range {%lu, %lu}. Aborting *** \n\n\n", (unsigned long)range.location, (unsigned long)range.length, (unsigned long)selfRange.location, (unsigned long)selfRange.length);
        return;
    }

    if (substring.length > 0) {
        NSRange movingRange = NSMakeRange(range.location, substring.length);
        if (NSMaxRange(movingRange) > self.length) {
            return;
        }

        NSString *movingString = [self.string substringWithRange:movingRange];

        while (NSMaxRange(movingRange) < NSMaxRange(range)) {
            if ([movingString compare:substring options:NSCaseInsensitiveSearch] == NSOrderedSame) {
                [self addAttribute:NSBackgroundColorAttributeName value:hilightColor range:movingRange];
            }
            movingRange = NSMakeRange(movingRange.location + 1, substring.length);
            movingString = [self.string substringWithRange:movingRange];
        }
    } // This is fine...string leaves properly attributed.
}
like image 449
Reid Avatar asked Sep 20 '14 22:09

Reid


1 Answers

Thanks for writing this up... Thought I was going crazy too!

I came up with a workaround (read: hack) whilst we wait for something official from Apple.

NSDictionary *hackAttribute = [NSDictionary dictionaryWithObjectsAndKeys:
                           [UIColor clearColor], NSBackgroundColorAttributeName, nil];

NSMutableAttributedString *attributedText =
    [[NSMutableAttributedString alloc] initWithString:@"some text..."];
    [attributedAddressText setAttributes:hackAttribute range:NSMakeRange(0, attributedText.length)];
// Then set your other attributes as per normal

Hope that helps.

like image 191
Sam Wan Avatar answered Nov 16 '22 15:11

Sam Wan