Подтвердить что ты не робот

Выделите только текст в UILabel

Я пытаюсь установить цвет фона/выделить только текст в UILabel. Проблема заключается в том, что разрывы строк и пробелы, добавленные в UILabel, чтобы сохранить текст в центре, также выделяются.

enter image description here

Обратите внимание на интервал перед тем, как подсвечивается последняя строка в 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 будут постоянно отличаться, поэтому жесткое кодирование не является вариантом.

4b9b3361

Ответ 1

Мне показалось, что проблема была в перерыве. Моя идея состояла в том, чтобы попытаться узнать, когда UILabel добавит разрыв строки, а затем просто удалит этот символ из диапазона выделенных символов.

Похоже, вы не можете просто спросить UILabel, где разрывы строк будут, но вы можете проверить, какой будет размер NSString, когда вы добавите его на ярлык. Используя эту информацию, вы можете увеличивать каждый символ, постоянно проверяя высоту, и когда изменения высоты вы знаете, у вас есть новая строка.

Я привел пример, который берет строку Label и разделяет ее на отдельные строки, которые появятся в UILabel. Как только у меня есть каждая строка, я просто устанавливаю цвет фона для каждой строки, а не для всей строки. Это устраняет, а цвета фона устанавливаются при разрыве строк.

Есть, вероятно, более эффективные решения, и этот, вероятно, можно оптимизировать для повышения производительности, но это отправная точка и, похоже, работает.

Highlight words

- (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;
}

Ответ 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;
}

введите описание изображения здесь