仅突出显示 UILabel 中的文本

Posted

技术标签:

【中文标题】仅突出显示 UILabel 中的文本【英文标题】:Highlight just the text in a UILabel 【发布时间】:2015-07-08 13:16:51 【问题描述】:

我正在尝试设置背景颜色/仅突出显示 UILabel 中的文本。问题是添加到UILabel 以保持文本居中的换行符和空格也被突出显示。

注意UILabel 中最后一行之前的间距被突出显示。此外,任何新行的开头和结尾也会突出显示。

我正在使用以下代码创建上面的示例:

-(void)createSomeLabel 
    // Create and position my label
    UILabel *someLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,
                                                                   0,
                                                                   self.view.frame.size.width - 40,
                                                                   self.view.frame.size.height - 300)];
    someLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    someLabel.textAlignment = NSTextAlignmentCenter;
    someLabel.textColor = [UIColor whiteColor];
    someLabel.lineBreakMode = NSLineBreakByWordWrapping;
    someLabel.numberOfLines = 0;
    [self.view addSubview:someLabel];

    // This string will be different lengths all the time
    NSString *someLongString = @"Here is a really long amount of text that is going to wordwrap/line break and I don't want to highlight the spacing. I want to just highlight the words and a single space before/after the word";

    // Create attributed string
    NSMutableAttributedString *someLongStringAttr=[[NSMutableAttributedString alloc] initWithString:someLongString attributes:nil];

    // Apply background color
    [someLongStringAttr addAttribute:NSBackgroundColorAttributeName
                      value:[UIColor colorWithWhite:0 alpha:0.25]
                      range:NSMakeRange(0, someLongStringAttr.length)];

    // Set text of label
    someLabel.attributedText = someLongStringAttr;

我想要实现的输出是仅突出显示文本和单词之间的空格(如果只有一个空格)。文本的长度和UILabel 的大小会不断变化,所以很遗憾,硬编码解决方案不是一种选择。

【问题讨论】:

iosdevelopertips.com/user-interface/… @PavanJangid 我看不出这些示例与我提供的示例有何不同。你能详细说明一下吗? @DanielStorm 您是否尝试为每个单词的范围而不是整个字符串设置背景颜色? @Kreiri 我想到了这一点,但无法想出一种方法来从字符串中提取每个单词,两边都有空格,应用属性,然后重建字符串。我想当自动换行时,填充每个单词的空格会被拉伸,导致同样的问题? 建议使用 CoreText。一个好的开始:github.com/dineshrajas/HighlightLabel 似乎既然它计算了“真实的字母渲染”,它应该做你想做的事。不久前我在这个项目上玩了一点,如果我没记错的话,还有一些不完善的地方,还有一些改变要做。 【参考方案1】:

在我看来,换行是问题所在。 我的想法是尝试知道 UILabel 何时会添加换行符,然后从突出显示的字符范围中删除该字符。

您似乎不能只询问 UILabel 换行符将在哪里,但您可以检查将 NSString 添加到标签时的大小。 使用此信息,您可以不断检查每个字符的高度,当高度发生变化时,您知道您有一个新行。

我已经做了一个例子,它采用标签的字符串并将其分成单独的行,这些行将出现在 UILabel 中。一旦我有了每一行,我只需在每一行而不是整个字符串上设置背景颜色。这消除了在换行符处设置的背景颜色。

可能有更好的解决方案,并且这个解决方案可能会进行优化以获得更好的性能,但它只是一个起点,而且似乎可行。

- (void)createSomeLabel 
    // Create and position my label
    UILabel *someLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,
                                                                   0,
                                                                   self.view.frame.size.width - 40,
                                                                   self.view.frame.size.height - 300)];
    someLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    someLabel.textAlignment = NSTextAlignmentCenter;
    someLabel.textColor = [UIColor whiteColor];
    someLabel.lineBreakMode = NSLineBreakByWordWrapping;
    someLabel.numberOfLines = 0;
    [self.view addSubview:someLabel];

    // This string will be different lengths all the time
    NSString *someLongString = @"Here is a really long amount of text that is going to wordwrap/line break and I don't want to highlight the spacing. I want to just highlight the words and a single space before/after the word";

    // Create attributed string
    NSMutableAttributedString *someLongStringAttr=[[NSMutableAttributedString alloc] initWithString:someLongString attributes:nil];


    // The idea here is to figure out where the UILabel would automatically make a line break and get each line of text separately.
    // Temporarily set the label to be that string so that we can guess where the UILabel naturally puts its line breaks.
    [someLabel setText:someLongString];
    // Get an array of each individual line as the UILabel would present it.
    NSArray *allLines = getLinesForLabel(someLabel);
    [someLabel setText:@""];


    // Loop through each line of text and apply the background color to just the text within that range.
    // This way, no whitespace / line breaks will be highlighted.
    __block int startRange = 0;
    [allLines enumerateObjectsUsingBlock:^(NSString *line, NSUInteger idx, BOOL *stop) 

        // The end range should be the length of the line, minus one for the whitespace.
        // If we are on the final line, there are no more line breaks so we use the whole line length.
        NSUInteger endRange = (idx+1 == allLines.count) ?  line.length : line.length-1;

        // Apply background color
        [someLongStringAttr addAttribute:NSBackgroundColorAttributeName
                                   value:[UIColor colorWithWhite:0 alpha:0.25]
                                   range:NSMakeRange(startRange, endRange)];

        // Update the start range to the next line
        startRange += line.length;
    ];



    // Set text of label
    someLabel.attributedText = someLongStringAttr;



#pragma mark - Utility Functions

static NSArray *getLinesForLabel(UILabel *label) 

    // Get the text from the label
    NSString *labelText = label.text;

    // Create an array to hold the lines of text
    NSMutableArray *allLines = [NSMutableArray array];

    while (YES) 

        // Get the length of the current line of text
        int length = getLengthOfTextInFrame(label, labelText) + 1;

        // Add this line of text to the array
        [allLines addObject:[labelText substringToIndex:length]];

        // Adjust the label text
        labelText = [labelText substringFromIndex:length];

        // Check for the final line
        if(labelText.length<length) 
            [allLines addObject:labelText];
            break;
        
    

    return [NSArray arrayWithArray:allLines];


static int getLengthOfTextInFrame(UILabel *label, NSString *text) 

    // Create a block for getting the bounds of the current peice of text.
    CGRect (^boundingRectForLength)(int) = ^CGRect(int length) 
        NSString *cutText = [text substringToIndex:length];
        CGRect textRect = [cutText boundingRectWithSize:CGSizeMake(label.frame.size.width, CGFLOAT_MAX)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                             attributes:@NSFontAttributeName : label.font
                                                context:nil];
        return textRect;
    ;

    // Get the frame of the string for one character
    int length = 1;
    int lastSpace = 1;
    CGRect textRect = boundingRectForLength(length);
    CGFloat oneLineHeight = CGRectGetHeight(textRect);

    // Keep adding one character to the string until the height changes, then you know you have a new line
    while (textRect.size.height <= oneLineHeight)
    
        // If the next character is white space, save the current length.
        // It could be the end of the line.
        // This will not work for character wrap.
        if ([[text substringWithRange:NSMakeRange (length, 1)] isEqualToString:@" "]) 
            lastSpace = length;
        

        // Increment length and get the new bounds
        textRect = boundingRectForLength(++length);
    

    return lastSpace;

【讨论】:

嗨@Sam,如果我的文本是Karen Neraka,我的行在第一行Karen ,第二行是Neraka,会发生什么。它会崩溃吗?因为第二行更长。 嗨@KarenAnne 在突出显示标签之前计算所有内容的前期成本很小,但它应该不明显,并且取决于文本的数量。为了节省性能,您可以在后台线程上运行任何不更新标签的代码。由于线路大小,应该没有问题或崩溃。这些函数应该能够计算出正确的大小并突出显示需要的内容。 感谢@Sam 的帮助。很高兴知道它不会在性能上花费太多。 关于崩溃,我们遇到了一些索引超出范围的崩溃,我们只是修改了代码以满足我们的需要!非常感谢你的帮助! @山姆? @KarenAnne 我也是越界异常,你还记得你改变了什么吗?【参考方案2】:

我也遇到过同样的问题,并找到了更简单的解决方案,而且没有巨大的性能成本。您可以简单地将TTTAttributedLabel 添加到您的项目中。

我的问题演示项目:

#import "TTTAttributedLabel.h"

@implementation ViewController

- (void)viewDidLoad

    [super viewDidLoad];

    UILabel *label1 = [UILabel new];
    label1.textAlignment = NSTextAlignmentCenter;
    label1.numberOfLines = 0;
    label1.frame = CGRectMake(20, 0, CGRectGetWidth(self.view.frame) - 40, CGRectGetHeight(self.view.frame) / 2.0);
    [self.view addSubview:label1];

    TTTAttributedLabel *label2 = [TTTAttributedLabel new];
    label2.textAlignment = NSTextAlignmentCenter;
    label2.numberOfLines = 0;
    label2.frame = CGRectMake(20, CGRectGetHeight(self.view.frame) / 2.0, CGRectGetWidth(self.view.frame) - 40, CGRectGetHeight(self.view.frame) / 2.0);
    [self.view addSubview:label2];

    NSDictionary *attributes = @NSBackgroundColorAttributeName:[UIColor blackColor], NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:32 weight:UIFontWeightBold];
    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"Some very long string which can contain newlines and some other stuff" attributes:attributes];
    label1.attributedText = string;
    label2.text = string;


@end

【讨论】:

【参考方案3】:

从 iOS 10.3 开始,有问题的相同代码现在会产生所需的结果。不确定这是错误还是新功能。

-(void)createSomeLabel 
    // Create and position my label
    UILabel *someLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,
                                                                   0,
                                                                   self.view.frame.size.width - 40.0,
                                                                   self.view.frame.size.height - 300.0)];
    someLabel.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
    someLabel.textAlignment = NSTextAlignmentCenter;
    someLabel.textColor = [UIColor whiteColor];
    someLabel.lineBreakMode = NSLineBreakByWordWrapping;
    someLabel.numberOfLines = 0;
    [self.view addSubview:someLabel];

    // This string will be different lengths all the time
    NSString *someLongString = @"Here is a really long amount of text that is going to wordwrap/line break and I don't want to highlight the spacing. I want to just highlight the words and a single space before/after the word";

    // Create attributed string
    NSMutableAttributedString *someLongStringAttr = [[NSMutableAttributedString alloc] initWithString:someLongString attributes:nil];

    // Apply background color
    [someLongStringAttr addAttribute:NSBackgroundColorAttributeName
                               value:[UIColor colorWithWhite:0 alpha:0.25]
                               range:NSMakeRange(0, someLongStringAttr.length)];

    // Set text of label
    someLabel.attributedText = someLongStringAttr;

【讨论】:

错误通常不会让事情变得更好。 :) @NRitH 哈哈。我提到这更像是一个警告。到目前为止,我不会认为这是稳定的。我们需要更多时间或一些文档来确认此代码是安全的。 又回到了 iOS 11.0 之前的样子。

以上是关于仅突出显示 UILabel 中的文本的主要内容,如果未能解决你的问题,请参考以下文章

在表格中突出显示 UILabel

UILabel 粗体/突出显示所有出现的子字符串

突出显示时将 UILabel 字体更改为粗体

使用 Javascript 突出显示页面上的文本

三星 Galaxy Tab 中的 Sencha touch Textfield 突出显示行为

Excel 公式或规则或 vba 比较 2 个单元格并仅突出显示差异