截断多行NSTextField的最后一行

时间:2012-06-21 13:03:35

标签: cocoa nstextfield nstextfieldcell

我正在尝试创建一个类似于Finder文件标签的文本字段。我希望最后一行(第二行)在中间截断。

我开始使用多行NSTextField

但是,调用[self.cell setLineBreakMode:NSLineBreakByTruncatingMiddle];会导致文本字段仅显示单个截断行(不再有换行符)。

以下是Finder的样子:

Finder example

2 个答案:

答案 0 :(得分:6)

如果你想像finder标签那样包装文本,那么使用两个标签对你没有任何好处,因为你需要知道第一行的最大可破坏文本量是多少。另外,如果您正在构建会显示大量项目的内容,则两个标签会不必要地使GUI过载。

像这样设置你的NSTextField.cell:

[captionLabel.cell setLineBreakMode: NSLineBreakByCharWrapping];

然后找到“NS(Attributed)String + Geometrics”的代码(Google it,它就在那里)。你必须#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;
}

干杯!

PS测试原型作品很可能有bug ...找到它们

答案 1 :(得分:0)

我怀疑那里有两个标签。顶部包含文件名的前20个字符,第二个包含任何溢出,截断。

第一个标签的长度可能会受到用户字体设置的限制。