2012-06-21 2 views
3

Я пытаюсь создать текстовое поле, подобное меткам файла Finder. Я хотел бы, чтобы последняя (вторая) линия была усечена посередине.Усечь последнюю строку многострочного NSTextField

Я начал с многострочной линии NSTextField.

Тем не менее, при вызове [self.cell setLineBreakMode:NSLineBreakByTruncatingMiddle]; в текстовом поле отображается только одна усеченная линия (без перерывов на линии больше).

Вот как это выглядит в Finder:

Finder example

ответ

5

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

Установите NSTextField.cell так:

[captionLabel.cell setLineBreakMode: NSLineBreakByCharWrapping]; 

Затем найдите код "NS (Приписываемые) Струнный + Geometrics" (Google это, это там). Вы должны #import "NS (Attributed) String + Geometrics.h" для измерения текста. Это обезьяны патчей NSString и NSAttributedString

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

Во-первых, это, как вы назовете следующий код в код:

NSString *caption = self.textInput.stringValue; 
CGFloat w = self.captionLabel.bounds.size.width; 
NSString *wrappedCaption = [self wrappedCaptionText:self.captionLabel.font caption:caption width:w]; 
self.captionLabel.stringValue = wrappedCaption ? [self middleTruncatedCaption:wrappedCaption withFont:self.captionLabel.font width:w] : caption; 

Теперь для основного кода:

#define SINGLE_LINE_HEIGHT 21 

/* 
    This is the way finder captions work - 

    1) see if the string needs wrapping at all 
    2) if so find the maximum amount that will fit on the first line of the caption 
    3) See if there is a (word)break character somewhere between the maximum that would fit on the first line and the begining of the string 
    4) If there is a break character (working backwards) on the first line- insert a line break then return a string so that the truncation function can trunc the second line 
*/ 

-(NSString *) wrappedCaptionText:(NSFont*) aFont caption:(NSString*)caption width:(CGFloat)captionWidth 
{ 
    NSString *wrappedCaption = nil; 

    //get the width for the text as if it was in a single line 
    CGFloat widthOfText = [caption widthForHeight:SINGLE_LINE_HEIGHT font:aFont]; 

    //1) nothing to wrap 
    if (widthOfText <= captionWidth) 
     return nil; 

    //2) find the maximum amount that fits on the first line 
    NSRange firstLineRange = [self getMaximumLengthOfFirstLineWithFont:aFont caption:caption width:captionWidth]; 

    //3) find the first breakable character on the first line looking backwards 
    NSCharacterSet *notAlphaNums = [NSCharacterSet alphanumericCharacterSet].invertedSet; 
    NSCharacterSet *whites = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 

    NSRange range = [caption rangeOfCharacterFromSet:notAlphaNums options:NSBackwardsSearch range:firstLineRange]; 

    NSUInteger splitPos; 
    if ((range.length == 0) || (range.location < firstLineRange.length * 2/3)) { 
     // no break found or break is too (less than two thirds) far to the start of the text 
     splitPos = firstLineRange.length; 
    } else { 
     splitPos = range.location+range.length; 
    } 

    //4) put a line break at the logical end of the first line 
    wrappedCaption = [NSString stringWithFormat:@"%@\n%@", 
         [[caption substringToIndex:splitPos] stringByTrimmingCharactersInSet:whites], 
         [[caption substringFromIndex:splitPos] stringByTrimmingCharactersInSet:whites]]; 

    return wrappedCaption; 
} 

/* 
    Binary search is great..but when we split the caption in half, we dont have far to go usually 
    Depends on the average length of text you are trying to wrap filenames are not usually that long 
    compared to the captions that hold them... 
*/ 

-(NSRange) getMaximumLengthOfFirstLineWithFont:(NSFont *)aFont caption:(NSString*)caption width:(CGFloat)captionWidth 
{ 
    BOOL fits = NO; 
    NSString *firstLine = nil; 
    NSRange range; 
    range.length = caption.length /2; 
    range.location = 0; 
    NSUInteger lastFailedLength = caption.length; 
    NSUInteger lastSuccessLength = 0; 
    int testCount = 0; 
    NSUInteger initialLength = range.length; 
    NSUInteger actualDistance = 0; 

    while (!fits) { 
     firstLine = [caption substringWithRange:range]; 

     fits = [firstLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont] < captionWidth; 

     testCount++; 

     if (!fits) { 
      lastFailedLength = range.length; 
      range.length-= (lastFailedLength - lastSuccessLength) == 1? 1 : (lastFailedLength - lastSuccessLength)/2; 
      continue; 
     } else { 
      if (range.length == lastFailedLength -1) { 
       actualDistance = range.length - initialLength; 
       #ifdef DEBUG 
        NSLog(@"# of tests:%d actualDistance:%lu iteration better? %@", testCount, (unsigned long)actualDistance, testCount > actualDistance ? @"YES" :@"NO"); 
       #endif 
       break; 
      } else { 
       lastSuccessLength = range.length; 
       range.length += (lastFailedLength-range.length)/2; 
       fits = NO; 
       continue; 
      } 
     } 
    } 

    return range; 
} 

-(NSString *)middleTruncatedCaption:(NSString*)aCaption withFont:(NSFont*)aFont width:(CGFloat)captionWidth 
{ 
    NSArray *components = [aCaption componentsSeparatedByString:@"\n"]; 
    NSString *secondLine = [components objectAtIndex:1]; 
    NSString *newCaption = aCaption; 

    CGFloat widthOfText = [secondLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont]; 
    if (widthOfText > captionWidth) { 
     //ignore the fact that the length might be an odd/even number "..." will always truncate at least one character 
     int middleChar = ((int)secondLine.length-1)/2; 

     NSString *newSecondLine = nil; 
     NSString *leftSide = secondLine; 
     NSString *rightSide = secondLine;   

     for (int i=1; i <= middleChar; i++) { 
      leftSide = [secondLine substringToIndex:middleChar-i]; 
      rightSide = [secondLine substringFromIndex:middleChar+i]; 

      newSecondLine = [NSString stringWithFormat:@"%@…%@", leftSide, rightSide]; 

      widthOfText = [newSecondLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont]; 

      if (widthOfText <= captionWidth) { 
       newCaption = [NSString stringWithFormat:@"%@\n%@", [components objectAtIndex:0], newSecondLine]; 
       break; 
      } 
     } 
    } 

    return newCaption; 
} 

@end 

Ура!

PS Протестировано в прототипе работает отлично, возможно, есть ошибки ... найти их

+0

Хорошо, я сдаюсь - я не могу заставить его даже скомпилировать. Возможно, я использую неправильный патч (я Googled и получил его с http://code.google.com/p/lyricus/source/browse/trunk/NS%28Attributed%29String%2BGeometrics.m?r=60), возможно Я неправильно настраиваю свой интерфейс, используя неправильные объекты или имена привязок, поскольку они не были объяснены.Любой шанс, который вы могли бы предоставить немного более подробно, как и полное содержимое ваших файлов .h и .m и где именно вы получаете NS (Attributed) String + Geometrics? –

+0

Этот код в коде Google должен работать. Просто добавьте .h и .m в свой проект и используйте приведенный выше код. Чтобы вызвать код (см. Первые две строки ответа - часть, которая, вероятно, отключает вас, - это заголовок экземпляра var, и он используется методы изнутри, причина в том, что класс, который я использую, представляет собой элемент в поисковом элементе, в виде сетки которого заголовок является ivar. Если вы хотите сделать из него более общую утилиту, создайте класс утилиты и передайте в –

+0

Я обновил код, исправляя некоторые проблемы: теперь он правильно разбивается на разбитые текстовые поля, которые теперь представляют собой любой символ без буквы и цифры, а не только пробел. Я также изменил его так, чтобы текст субтитров и максимальный width теперь передаются как аргументы. –

0

Я подозреваю, что есть две метки там. Верхний содержит первые 20 символов имени файла, а второй содержит любое переполнение, усеченное.

Длина первой метки, вероятно, ограничена на основе настроек шрифта пользователя.

+0

Это одна из возможностей. Тогда волшебный вопрос заключается в том, как определить количество символов, которые соответствуют первой строке ... – Mark

+0

Ну, простой подход - это измерение размера строк, каждый из которых длиннее последнего, до тех пор, пока он не заполнит доступное пространство - Затем перейдите к следующей строке вручную. Это может быть дорого, хотя, если это делается часто. Вы случайно взглянули на Core Text? –

+0

В прошлом я использовал 'NSAttributedString size' и' boundingRectWithSize: options: ', но ни один не дал точных результатов (т. Е. Правильной ширины, которая фактически использовалась текстовым полем). – Mark

Смежные вопросы