Результирующие строки UILabel с UILineBreakModeWordWrap

У меня есть UILabel, размер которого вычисляется с помощью метода sizeWithFont:. Режим разрыва строки установлен на UILineBreakModeWordWrap (тот же флаг используется при расчете размера с помощью sizeWithFont:)...

Все отлично работает, ярлык имеет нужный размер и отображает текст по мере необходимости.

Теперь мне нужно знать строки, которые используются для отображения метки (или строк, которые генерируются при использовании sizeWithFont:). Я мог бы технически написать собственную реализацию разрыва строки на основе возвратов пространств/кареток, но тогда это не будет гарантировано так же, как реализация Apple, и, следовательно, результирующие строки не будут теми, которые используются для вычисления размера текста, не забывайте о том, как изобретать колесо.

В идеале я бы передал свою строку, задал режим ширины и разрыва строки и получал массив строк, представляющих визуальные строки текста.

Любые идеи, как сделать это самым элегантным способом?

Ответ 1

Я не думаю, что для этого есть серебряная пуля.

Вот метод категории, который, похоже, работает для нескольких базовых тестов, которые я бросил на него. Нет гарантий, что он не сломается с чем-то сложным!

Как это работает, нужно пройти через тестирование строк, чтобы узнать, подходит ли диапазон слов по ширине метки. Когда он вычисляет, что текущий диапазон слишком широк, он записывает диапазон последнего соответствия как строку.

Я не утверждаю, что это эффективно. Лучший способ может состоять в том, чтобы реализовать собственный UILabel...

@interface UILabel (Extensions)

- (NSArray*) lines;

@end

@implementation UILabel (Extensions)

- (NSArray*) lines
{
    if ( self.lineBreakMode != UILineBreakModeWordWrap )
    {
        return nil;
    }

    NSMutableArray* lines = [NSMutableArray arrayWithCapacity:10];

    NSCharacterSet* wordSeparators = [NSCharacterSet whitespaceAndNewlineCharacterSet];

    NSString* currentLine = self.text;
    int textLength = [self.text length];

    NSRange rCurrentLine = NSMakeRange(0, textLength);
    NSRange rWhitespace = NSMakeRange(0,0);
    NSRange rRemainingText = NSMakeRange(0, textLength);
    BOOL done = NO;
    while ( !done )
    {
        // determine the next whitespace word separator position
        rWhitespace.location = rWhitespace.location + rWhitespace.length;
        rWhitespace.length = textLength - rWhitespace.location;
        rWhitespace = [self.text rangeOfCharacterFromSet: wordSeparators options: NSCaseInsensitiveSearch range: rWhitespace];
        if ( rWhitespace.location == NSNotFound )
        {
            rWhitespace.location = textLength;
            done = YES;
        }

        NSRange rTest = NSMakeRange(rRemainingText.location, rWhitespace.location-rRemainingText.location);

        NSString* textTest = [self.text substringWithRange: rTest];

        CGSize sizeTest = [textTest sizeWithFont: self.font forWidth: 1024.0 lineBreakMode: UILineBreakModeWordWrap];
        if ( sizeTest.width > self.bounds.size.width )
        {
            [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]];
            rRemainingText.location = rCurrentLine.location + rCurrentLine.length;
            rRemainingText.length = textLength-rRemainingText.location;
            continue;
        }

        rCurrentLine = rTest;
        currentLine = textTest;
    }

    [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]];

    return lines;
}

@end

используйте следующее:

NSArray* lines = [_theLabel lines];

int count = [lines count];

Ответ 2

Чтобы вычислить количество строк, которые a UILabel имеет после обертывания текста, вам нужно будет найти ведущую (высоту строки) вашего шрифта UILabel (label.font.leading), а затем разделить высоту вашего мульти- line UILabel по высоте каждой строки, чтобы получить количество строк.

Вот пример:

- (void)viewDidLoad {

    [super viewDidLoad];

    UILabel *label = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
    label.numberOfLines = 0;
    label.lineBreakMode = UILineBreakModeWordWrap;  
    label.text = @"Some really really long string that will cause the label text to wrap and wrap and wrap around. Some really really long string that will cause the label text to wrap and wrap and wrap around.";

    CGRect frame = label.frame;
    frame.size.width = 150.0f;
    frame.size = [label sizeThatFits:frame.size];
    label.frame = frame;

    CGFloat lineHeight = label.font.leading;
    NSUInteger linesInLabel = floor(frame.size.height/lineHeight);
    NSLog(@"Number of lines in label: %i", linesInLabel);

    [self.view addSubview:label];

}

Или вы можете сделать это в двух строках:

[label sizeToFit];
int numLines = (int)(label.frame.size.height/label.font.leading);

Ответ 3

Просто позвоните ниже и передайте либо UILabel, либо UITextView:

-(NSInteger)getNumberOfLinesInLabelOrTextView:(id)obj
{
    NSInteger lineCount = 0;
    if([obj isKindOfClass:[UILabel class]])
    {
        UILabel *label = (UILabel *)obj;

       // This method is deprecated in iOS 7.0 or later 
       // CGSize requiredSize = [label.text sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode]; 

        CGSize requiredSize = [label.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(label.frame), CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:label.font} context:nil].size;

        int charSize = label.font.leading;
        int rHeight = requiredSize.height;

        lineCount = rHeight/charSize;
    }
    else if ([obj isKindOfClass:[UITextView class]])
    {
        UITextView *textView = (UITextView *)obj;
        lineCount = textView.contentSize.height / textView.font.leading;
    }

    return lineCount;
}

Теперь вызовите этот метод: -

NSLog(@"%d",[self getNumberOfLinesInLabelOrTextView:label]);
NSLog(@"%d",[self getNumberOfLinesInLabelOrTextView:textView]);

ОБНОВЛЕНО: SWIFT CODE

func getNumberOfLinesInLabelOrTextView(obj:AnyObject) -> NSInteger {

    var lineCount: NSInteger = 0
    if (obj.isKindOfClass(UILabel)) {

        let label: UILabel = obj as! UILabel
        let requiredSize: CGSize = (label.text)!.boundingRectWithSize(CGSizeMake(CGRectGetWidth(label.frame), CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: label.font], context: nil).size
        let charSize: CGFloat = label.font.leading
        let rHeight: CGFloat = requiredSize.height
        lineCount = (NSInteger)(rHeight/charSize)
    }
    else if (obj.isKindOfClass(UITextView)){

        let textView: UITextView = obj as! UITextView
        lineCount = (NSInteger)(textView.contentSize.height / textView.font.leading)
    }

    return lineCount
}

Теперь вызовите этот метод: -

println("%d \(self.getNumberOfLinesInLabelOrTextView(textView))")
println("%d \(self.getNumberOfLinesInLabelOrTextView(label))")

Примечание: leading - используйте lineHeight. не возвращает фактическое руководство. будет формально устаревшим в будущем.

Ответ 4

для Xcode 7 и выше Ответ TheTiger требует обновления, прокомментированного следующим кодом:

-(NSInteger)getNumberOfLinesInLabelOrTextView:(id)obj
{
    NSInteger lineCount = 0;
    if([obj isKindOfClass:[UILabel class]])
    {
        UILabel *label = (UILabel *)obj;
        CGSize requiredSize = [label.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(label.frame), CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:label.font} context:nil].size;

        int charSize = label.font.leading;
        // now listen , you need to set the text or label with only 1 
        // then nslog(@"%d",charSize);
        // then change the line int charSize = label.font.leading; into 
        // int charSize = the printed value in case of 1 line
        int rHeight = requiredSize.height;

        lineCount = rHeight/charSize;
    }
    else if ([obj isKindOfClass:[UITextView class]])
    {
        UITextView *textView = (UITextView *)obj;
        lineCount = textView.contentSize.height / textView.font.leading;
    }

    return lineCount;
}

это будет работать, только если вы используете тот же шрифт и размер, это не умный ход, но он мне помог, и я хотел поделиться им как с текущим решением, которое я знаю.

Ответ 5

Обновлено для Swift 3

Чтобы вычислить количество строк, которые имеет UILabel, после обтекания текста вам нужно разделить высоту вашего многострочного UILabel на высоту каждой строки (ascender).

При попытке ответа Адольфо по какой-то причине label.font.leading возвращается 0.0, поэтому я использовал label.font.ascender, который возвращает высоту от базовой линии до вершины фрейма UILabel. См. Рисунок ниже.

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

//Makes label go to another line if necessary
label.numberOfLines = 0 //Set num of lines to infinity
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
let numLines = Int(label.frame.size.height/label.font.ascender)