6

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

我从多行开始NSTextField

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

这是 Finder 中的样子:

查找器示例

4

2 回答 2

6

如果您想像查找器标签一样包装文本,使用两个标签对您没有任何好处,因为您需要知道第一行的最大可破坏文本量是多少。另外,如果您正在构建将显示大量项目的东西,则两个标签会不必要地使 GUI 负担过重。

像这样设置你的 NSTextField.cell:

[captionLabel.cell setLineBreakMode: NSLineBreakByCharWrapping];

然后找到“NS(Attributed)String+Geometrics”的代码(谷歌它,它就在那里)。您必须 #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 在原型中测试效果很好可能有错误...找到它们

于 2012-07-30T12:45:30.647 回答
0

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

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

于 2012-06-21T13:12:07.970 回答