UITextView - Highlight text with NSBackgroundColor

2019-04-01 07:34发布

问题:

I have a working feature with text highlighting, the problem is that it also highlights line break. See the image:

Below is a function I use for highlighting:

   -(void)setHighlight{

        //set highlighted

        __block BOOL textIsHighlited = YES;

        [self.attributedText enumerateAttributesInRange:[self selectedRange] options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
            if ([attrs valueForKey:@"NSBackgroundColor"] == Nil) {
                textIsHighlited = NO;
            }
        }];

        if (textIsHighlited) {
            [self.textStorage removeAttribute:NSBackgroundColorAttributeName range:[self selectedRange]];
        }else{
            [self.textStorage addAttribute:NSBackgroundColorAttributeName value:[UIColor greenColor] range:[self selectedRange]];
        }
    }

Is there any simple solution? Should I divide the string before line breaks and highlight them separately? Also note that the strings are editable by user, so there would have to be some logic to check if the text didn't break while editing other part of it.

Thanks for any suggestions.

回答1:

My solution is not simple and I do not know if it solves all your problems, but I managed to achieve the effect you wanted.

I've tested it on UILabel, on different fonts and font sizes, and it worked well.

Here what i've done:

- (NSArray*)getRangesOfLinesForText:(NSString*)text font:(UIFont*)font containerWidth:(float)width {

    NSMutableArray *ranges = [[NSMutableArray alloc] init];

    NSInteger lastWhiteSpaceIndex = 0;
    NSInteger rangeStart = 0;
    NSMutableString *substring = [[NSMutableString alloc] init];
    for (int i = 0; i < [text length]; i++) {

        char c = [text characterAtIndex:i];
        [substring appendFormat:@"%c",c];

        CGRect substringRect = [substring boundingRectWithSize:CGSizeMake(width, font.capHeight)options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : font} context:nil];

        if([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:c]) {
            lastWhiteSpaceIndex = i;
        }

        if(substringRect.size.width == 0) {
            NSRange range;
            if (lastWhiteSpaceIndex != i) {

                range = NSMakeRange(rangeStart, lastWhiteSpaceIndex-rangeStart);

                [ranges addObject:NSStringFromRange(range)];
                substring = [[NSMutableString alloc] init];
                i = lastWhiteSpaceIndex;
                rangeStart = lastWhiteSpaceIndex+1;
            }
        }
    }

    //Last Line
    NSRange range = NSMakeRange(rangeStart, [text length]-rangeStart);
    [ranges addObject:NSStringFromRange(range)];

    return ranges;
}

The method above splits the text string into separate lines. Unfortunately the method boundingRectWithSize:options:attributes:context: does not support line break by word wrap, so i had to detect it myself. I achieved that by checking if substringRect.size.width == 0. (It changes to zero, when substring becomes too long to fit the line width).

The method returns an array of ranges for every line. (Ranges are converted to NSStrings with NSStringFromRange).

Example of use:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectFromString(@"{{0,0},{300,400}}")];
    textLabel.numberOfLines = 0;
    [self.view addSubview:textLabel];


    NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus quis sapien a rutrum. Vivamus nec leo suscipit nibh rutrum dignissim at vel justo. Maecenas mi orci, ultrices non luctus nec, aliquet et nunc.";
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:text];

    for (NSString* stringRange in [self getRangesOfLinesForText:text font:textLabel.font containerWidth:textLabel.frame.size.width]) {

        [string addAttribute:NSBackgroundColorAttributeName value:[UIColor greenColor] range:NSRangeFromString(stringRange)];

    }

    textLabel.attributedText = string;
}


回答2:

See TextViewHighlighter

Basically, we need to detect the ranges of line break characters, and make attributes for them to NSBackgroundColorAttributeName: [UIColor clearColor]

NSMutableAttributedString *highlighedAttributedString = [[NSMutableAttributedString alloc] initWithString:self.text];
    [highlighedAttributedString addAttributes:@{NSForegroundColorAttributeName: self.textColor,
                                                NSBackgroundColorAttributeName: self.highlightedColor
                                                }
                                        range:NSMakeRange(0, self.text.length)];

    for (NSValue *rangeValue in rangeValues) {
        NSRange range = [rangeValue rangeValue];
        [highlighedAttributedString addAttributes:@{NSBackgroundColorAttributeName: [UIColor clearColor],
                                                    }
                                            range:range];
    }