如何在文本单元格中设置字体大小,以便字符串填充单元格的rect?

时间:2010-03-09 15:58:25

标签: cocoa text nstextfieldcell

我有一个包含两个NSTextFieldCell的视图。绘制这些单元格的大小是从视图的大小派生的,我希望每个单元格中的文本最大,以适合单元格的派生大小。这是我的,没有设置字体大小:

- (void)drawRect:(NSRect)dirtyRect {
    /*
     * Observant readers will notice that I update the whole view here. If
     * there is a perceived performance problem, then I'll switch to just
     * updating the dirty rect.
     */
    NSRect boundsRect = self.bounds;
    const CGFloat monthHeight = 0.25 * boundsRect.size.height;
    NSRect monthRect = NSMakeRect(boundsRect.origin.x,
                                  boundsRect.origin.y + boundsRect.size.height
                                  - monthHeight,
                                  boundsRect.size.width,
                                  monthHeight);
    [monthCell drawWithFrame: monthRect inView: self];

    NSRect dayRect = NSMakeRect(boundsRect.origin.x,
                                boundsRect.origin.y,
                                boundsRect.size.width,
                                boundsRect.size.height - monthHeight);
    [dayCell drawWithFrame: dayRect inView: self];

    [[NSColor blackColor] set];
    [NSBezierPath strokeRect: boundsRect];
}

所以我知道我可以询问字符串给定属性需要的大小,我知道我可以让控件改变其大小以适应其内容。这些都不适用:我希望内容(在这种情况下,单元格的stringValue)的大小适合已知的rect尺寸,以及实现未知的所需属性。我怎样才能找到所需尺寸?假设我知道我将使用什么字体(因为我这样做)。

更新注意:我不想截断字符串,我想成长收缩这样整个事物就可以用最大的文本大小适合提供的矩形。

6 个答案:

答案 0 :(得分:5)

我使用了一些类似的代码,但它处理不同的字体,大小不超过10,000,并考虑了可用的高度以及文本显示区域的宽度。

#define kMaxFontSize    10000

- (CGFloat)fontSizeForAreaSize:(NSSize)areaSize withString:(NSString *)stringToSize usingFont:(NSString *)fontName;
{
    NSFont * displayFont = nil;
    NSSize stringSize = NSZeroSize;
    NSMutableDictionary * fontAttributes = [[NSMutableDictionary alloc] init];

    if (areaSize.width == 0.0 || areaSize.height == 0.0) {
        return 0.0;
    }

    NSUInteger fontLoop = 0;
    for (fontLoop = 1; fontLoop <= kMaxFontSize; fontLoop++) {
        displayFont = [[NSFontManager sharedFontManager] convertWeight:YES ofFont:[NSFont fontWithName:fontName size:fontLoop]];
        [fontAttributes setObject:displayFont forKey:NSFontAttributeName];
        stringSize = [stringToSize sizeWithAttributes:fontAttributes];

        if (stringSize.width > areaSize.width)
            break;
        if (stringSize.height > areaSize.height)
            break;
    }

    [fontAttributes release], fontAttributes = nil;

    return (CGFloat)fontLoop - 1.0;
}

答案 1 :(得分:2)

建议在带外尝试二进制搜索以获得合适的大小。这是一个非常有限的例子:

- (NSFont *)labelFontForText: (NSString *)text inRect: (NSRect)rect {
    CGFloat prevSize = 0.0, guessSize = 16.0, tempSize;
    NSFont *guessFont = nil;
    while (fabs(guessSize - prevSize) > 0.125) {
        guessFont = [NSFont labelFontOfSize: guessSize];
        NSSize textSize = [text sizeWithAttributes: 
                            [NSDictionary dictionaryWithObject: guessFont
                                                        forKey: NSFontAttributeName]];
        if (textSize.width > rect.size.width || 
            textSize.height > rect.size.height) {
            tempSize = guessSize - (guessSize - prevSize) / 2.0;
        }
        else {
            tempSize = guessSize + (guessSize - prevSize) / 2.0;
        }
        prevSize = guessSize;
        guessSize = tempSize;
    }
    return [[guessFont retain] autorelease];
}

限制(你最好不需要32pt或更大的字体,或任何不是Lucida Grande的东西)对我的需求并不重要,但肯定会让一些人不使用这种方法。我会把问题保持开放,接受更强大的方法。

答案 2 :(得分:1)

这是一种不做猜测和检查的方法。根据字体的不同,可能需要一点填充来防止溢出(sizeWithAttributes不能完美缩放)。吊杆!

-(float)scaleToAspectFit:(CGSize)source into:(CGSize)into padding:(float)padding
{
    return MIN((into.width-padding) / source.width, (into.height-padding) / source.height);
}

-(NSFont*)fontSizedForAreaSize:(NSSize)size withString:(NSString*)string usingFont:(NSFont*)font;
{
    NSFont* sampleFont = [NSFont fontWithDescriptor:font.fontDescriptor size:12.];//use standard size to prevent error accrual
    CGSize sampleSize = [string sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:sampleFont, NSFontAttributeName, nil]];
    float scale = [self scaleToAspectFit:sampleSize into:size padding:10];
    return [NSFont fontWithDescriptor:font.fontDescriptor size:scale * sampleFont.pointSize];
}

答案 3 :(得分:0)

两个想法。一个我尝试过,另一个可能会工作:

1)在这个问题中做:How to truncate an NSString based on the graphical width?,即尝试不同尺寸,直到它不再适合

2)创建单元格,给它最大的矩形并将其设置为适合其文本进入单元格,然后询问它的理想大小(那里有一种方法可以做到)然后再次调整单元格的大小。最后,如果我正确理解你的问题。

答案 4 :(得分:0)

在我的情况下,我使用以下内容:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{

    //Create attributes
    NSColor *text_color = nil;
    NSFont *font = [self font];
    NSString *fontName = [font fontName];
    double fontSize = [font pointSize];

    NSInteger text_size = (int) fontSize;

    if([self isHighlighted])
        text_color = [NSColor colorWithCalibratedRed:1 green:1 blue:1 alpha:1];
    else
        text_color = [NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:1];


    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSFont fontWithName:fontName size:fontSize], NSFontAttributeName,
                                text_color, NSForegroundColorAttributeName,
                                nil];


    NSAttributedString * currentText=[[NSAttributedString alloc] initWithString:[self title] attributes: attributes];

    NSSize attrSize = [currentText size];

    while (attrSize.width > cellFrame.size.width && --text_size > 0) {


        attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                      [NSFont fontWithName:fontName size:text_size], NSFontAttributeName,
                      text_color, NSForegroundColorAttributeName,
                      nil];

        currentText=[[NSAttributedString alloc] initWithString:[self title] attributes: attributes];

        attrSize = [currentText size];

    }

    switch ([self alignment]) {
        default:
        case NSLeftTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x,
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;

        case NSRightTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x + (cellFrame.size.width) - (attrSize.width),
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;

        case NSCenterTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x + (cellFrame.size.width /2) - (attrSize.width/2),
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;


    }




}

答案 5 :(得分:0)

抱歉:已经五年了。文字宽度可能不再是您清醒生活中最重要的问题。但是,我有答案;也许其他人会受益。

精确文本宽度大小的关键关键(这也适用于文本高度)是要意识到渲染文本的宽度当然会有所不同 - 但是线性! - 设置font-size属性。当具有线性函数时,不需要二进制搜索,或者挑选并测试所有可能的font-size属性值;一个人只需要确定图表上的两个点。

要准备,不要绘制字符串,而是计算渲染字符串的宽度,例如,文本大小为20,文本大小为40.这将为您提供线性函数“渲染字符串宽度为”的两个数据点文本大小属性的函数“。然后,推断以使字符串适合您当前需要的任何渲染宽度。

我发现这种方法可以统一产生良好和快速的结果。当然,对于字体的变化,有时您可能会获得在边界框边缘悬挂两到三个像素的字符 - 但这是字体设计的人工制品。精心设计的字体效果很好,即使使用疯狂的字体,通常也只能提供几个像素的边界余地。

以上是我上个月遇到此问题时使用的例程。随意使用此代码。

/******************************************************************************************/

//
//  text.m
//

/******************************************************************************************/

@interface drawtext : NSObject {

  // name of the font to be used
  NSString *fontname;

  // instantiations of that font, at size 20 and at size 40, and at the currently-best size
  NSFont   *font20, *font40, *font;

  // first sizing function: rendered string height as a function of the font-size attribute
  CGFloat mh, bh; 

  // second sizing function: rendered string width as a function of the font-size attribute
  CGFloat mw, bw; 

}

@end

/******************************************************************************************/

@implementation drawtext

/******************************************************************************************/

// CLASS METHODS

/******************************************************************************************/

// The caller specifies the text string (all capitals! no descenders!) to be drawn, the
// name of the font to use, the box in which to draw the text, and a border if desired.
//
// The routine returns the fontsize to be used, and the origin to be used for the
// "drawAtPoint" message. This will result in the largest rendition of the text string
// which meets the constraints.

+ (void) sizeText: (NSString *) captext   // the string of text to evaluate for font size
    usingFontName: (NSString *) fontname  // the string name of the font to be employed
            inBox: (NSRect)     box       // the containing box on the screen
       withBorder: (NSSize)     border    // the # of pixels to leave blank as X & Y borders
    usingFontSize: (CGFloat *)  fontsize  // (returned) what font-size to use
         atOrigin: (NSPoint *)  origin    // (returned) where to execute the drawAtPoint
{
  // let's start by redefining the containing box to presume the borders

  NSRect newBox;
  newBox.origin.x    = box.origin.x + border.width;
  newBox.origin.y    = box.origin.y + border.height;
  newBox.size.width  = box.size.width - 2.0 * border.width;
  newBox.size.height = box.size.height - 2.0 * border.height;

  // find out dimensions at font size = 20, then at font size = 40, to use for extrapolation

  NSSize s20, s40;

  NSFont *f20 = [NSFont fontWithName:fontname size:20];
  NSMutableAttributedString *mtext20 = [[NSMutableAttributedString alloc] initWithString:captext];
  [mtext20 addAttribute:NSFontAttributeName value:f20 range:NSMakeRange(0,[mtext20 length])];
  s20.width  = mtext20.size.width;
  s20.height = f20.capHeight;

  NSFont *f40 = [NSFont fontWithName:fontname size:40];
  NSMutableAttributedString *mtext40 = [[NSMutableAttributedString alloc] initWithString:captext];
  [mtext40 addAttribute:NSFontAttributeName value:f40 range:NSMakeRange(0,[mtext40 length])];
  s40.width  = mtext40.size.width;
  s40.height = f40.capHeight;

  // hsize is "font size to cause height of rendered string to match box height"
  // wsize is "font size to cause width of rendered string to match box width"

  CGFloat x1, x2, y1, y2, m, b, hsize, wsize;

  // cap height as function of text size, in y = mx + b format

  x1 = 20;
  y1 = s20.height;
  x2 = 40;
  y2 = s40.height;
  m  = ( y2 - y1 ) / ( x2 - x1 );
  b  = y1 - ( m * x1 );
  hsize = ( newBox.size.height - b ) / m;

  // string len as function of text size, y = mx + b format

  x1 = 20;
  y1 = s20.width;
  x2 = 40;
  y2 = s40.width;
  m  = ( y2 - y1 ) / ( x2 - x1 );
  b  = y1 - ( m * x1 );
  wsize = ( newBox.size.width - b ) / m;

  // choose the lesser of the two extrapolated font-sizes to fit the string into the box,
  // and at the same time, find the origin point at which to render the string
  //
  // if ( hsize < wsize ) { // there will be east-west spaces
  // else { // there will be north-south spaces

  *fontsize = fmin( hsize, wsize );

  NSSize  textSize;

  NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
  NSFont *f = [NSFont fontWithName:fontname size:*fontsize];
  [mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
  textSize.width  = mtext.size.width;
  textSize.height = f.capHeight;

  // don't forget "descender", as this is an all-caps string (strings with descenders are
  // left as an extra credit exercise for the reader :)
  origin->y = newBox.origin.y + f.descender + ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
  origin->x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
}

/******************************************************************************************/

// Like the previous routine, except the font size is specified by the caller (this is
// employed in the case it is desired that various text strings, in different containing
// boxes, are to be drawn in the same font size).

+ (void) placeText: (NSString *) captext   // the string of text to evaluate for positioning
     usingFontName: (NSString *) fontname  // the string name of the font to be employed
             inBox: (NSRect)     box       // the containing box on the screen
        withBorder: (NSSize)     border    // the # of pixels to leave blank as X & Y borders
     usingFontSize: (CGFloat)    fontsize  // (passed) what font-size to use
          atOrigin: (NSPoint *)  origin    // (returned) where to execute the drawAtPoint
{
  NSRect newBox;

  newBox.origin.x    = box.origin.x + border.width;
  newBox.origin.y    = box.origin.y + border.height;
  newBox.size.width  = box.size.width - 2.0 * border.width;
  newBox.size.height = box.size.height - 2.0 * border.height;

  NSSize  textSize;

  NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
  NSFont *f = [NSFont fontWithName:fontname size:fontsize];
  [mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
  textSize.width  = mtext.size.width;
  textSize.height = f.capHeight;

  // don't forget "descender", as this is an all-caps string
  origin->y = newBox.origin.y + f.descender + ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
  origin->x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
}

/******************************************************************************************/

// This routine actually draws the text (the previous routines only determine how it
// should be drawn).
//
// The second routine can be used to draw a string with attributes such as color (i.e., 
// attributes which don't affect the size of the rendered string).

+ (void) drawText: (NSString *)  captext   // the string of text to be drawn
    usingFontName: (NSString *)  fontname  // the string name of the font to be employed
      andFontSize: (CGFloat)     fontsize  // what font-size to use
         atOrigin: (NSPoint)     origin    // where to execute the drawAtPoint
{
  NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
  NSFont *f = [NSFont fontWithName:fontname size:fontsize];
  [mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
  [mtext drawAtPoint:origin];
}

+ (void) drawMText: (NSMutableAttributedString *) captext // the string of Mtext to be drawn
    usingFontName:  (NSString *)  fontname  // the string name of the font to be employed
      andFontSize:  (CGFloat)     fontsize  // what font-size to use
         atOrigin:  (NSPoint)     origin    // where to execute the drawAtPoint
{
  NSFont *f = [NSFont fontWithName:fontname size:fontsize];
  [captext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[captext length])];
  [captext drawAtPoint:origin];
}

/******************************************************************************************/

// INSTANCE METHODS

/******************************************************************************************/

// When you instantiate the object, you set the font; from this, you can elucidate the 
// first of the two sizing functions: rendered string height as a function of the 
// font-size attribute. The function is stored in the instance variables of the object,
// in the variables { mh, bh }, to be used with the classic "y(x) = mx + b" format, where:
//
//     y is rendered string height
//     m is mh
//     x is font size attribute
//     b is bh

- (id) initUsingFontName: (NSString *) fname   // string name of font to be employed
{
  if ( !self ) self = [super init];

  fontname = [[NSString alloc] initWithString:fname];

  font20 = [NSFont fontWithName:fontname size:20];
  font40 = [NSFont fontWithName:fontname size:40];

  // "cap height as function of text size", in y = mx + b format (mh is m, bh is b)

  CGFloat x1, x2, y1, y2;

  x1 = 20;
  y1 = font20.capHeight;
  x2 = 40;
  y2 = font40.capHeight;

  mh = ( y2 - y1 ) / ( x2 - x1 );
  bh = y1 - ( mh * x1 );

  return self;
}

/******************************************************************************************/

// After initializing the object, you size a text string; this stores a second sizing
// function: rendered string width as a function of the font-size attribute, in { mw, bw }.

- (void) sizeString: (NSString *) captext   // one string of text to evaluate for font size
{
  CGFloat x1, x2, y1, y2;

  NSMutableAttributedString *mtext = 
    [[NSMutableAttributedString alloc] initWithString:captext];

  [mtext addAttribute:NSFontAttributeName 
                value:font20 
                range:NSMakeRange(0,[mtext length])];

  x1 = 20;
  y1 = mtext.size.width;

  [mtext addAttribute:NSFontAttributeName 
                value:font40 
                range:NSMakeRange(0,[mtext length])];

  x2 = 40;
  y2 = mtext.size.width;

  // "string width as function of text size", in y = mx + b format (mw is m, bw is b)

  mw = ( y2 - y1 ) / ( x2 - x1 );
  bw = y1 - ( mw * x1 );
}

/******************************************************************************************/

// Then to draw the text string in a box, you use this routine, which will draw it at the 
// largest size possible given all the constraints, including the provided box and border.
//
// A similar routine is provided following this one, to draw a mutable string which may
// contain attributes, such as color, which do not affect the size of the rendered string.

- (void) drawString: (NSString *) captext   // string of text to be drawn
              inBox: (NSRect)     box       // containing box on the screen
         withBorder: (NSSize)     border    // # of pixels to leave blank as X & Y borders
{
  NSRect newBox;

  newBox.origin.x    = box.origin.x + border.width;
  newBox.origin.y    = box.origin.y + border.height;
  newBox.size.width  = box.size.width - 2.0 * border.width;
  newBox.size.height = box.size.height - 2.0 * border.height;

  // solve linear sizing functions for text size, and choose the smaller text size
  //
  // if ( hsize < wsize ) there will be east-west spaces
  // if ( wsize < hsize ) there will be north-south spaces

  CGFloat hsize, wsize, fontsize;

  hsize = ( newBox.size.height - bh ) / mh;
  wsize = ( newBox.size.width  - bw ) / mw;

  fontsize = fmin( hsize, wsize );

  font = [NSFont fontWithName:fontname size:fontsize];

  NSMutableAttributedString *mtext = 
    [[NSMutableAttributedString alloc] initWithString:captext];

  [mtext addAttribute:NSFontAttributeName value:font range:NSMakeRange(0,[mtext length])];

  // find the origin-point at which to render the given string,
  // so that the text is centered in the box

  NSSize textSize;

  textSize.width  = mtext.size.width;
  textSize.height = font.capHeight;

  NSPoint origin;

  origin.y = newBox.origin.y + font.descender + 
               ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
  origin.x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );

  [mtext drawAtPoint:origin];
}

/******************************************************************************************/

// To draw a mutable text string in a box (a string containing attributes e.g. color, which
// do not affect the sizing of the rendered string), use this routine.

- (void) drawMString: (NSMutableAttributedString *) captext // the M-string to be drawn
               inBox: (NSRect)     box       // containing box on the screen
          withBorder: (NSSize)     border    // # of pixels to leave blank as X & Y borders
{
  NSRect newBox;

  newBox.origin.x    = box.origin.x + border.width;
  newBox.origin.y    = box.origin.y + border.height;
  newBox.size.width  = box.size.width - 2.0 * border.width;
  newBox.size.height = box.size.height - 2.0 * border.height;

  // solve linear sizing functions for text size, and choose the smaller text size
  //
  // if ( hsize < wsize ) there will be east-west spaces
  // if ( wsize < hsize ) there will be north-south spaces

  CGFloat hsize, wsize, fontsize;

  hsize = ( newBox.size.height - bh ) / mh;
  wsize = ( newBox.size.width  - bw ) / mw;

  fontsize = fmin( hsize, wsize );

  font = [NSFont fontWithName:fontname size:fontsize];

  [captext addAttribute:NSFontAttributeName 
                  value:font 
                  range:NSMakeRange(0,[captext length])];

  // find the origin-point at which to render the given string,
  // so that the text is centered in the box

  NSSize textSize;

  textSize.width  = captext.size.width;
  textSize.height = font.capHeight;

  NSPoint origin;

  origin.y = newBox.origin.y + font.descender + 
               ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
  origin.x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );

  [captext drawAtPoint:origin];
}

/******************************************************************************************/

@end

/******************************************************************************************/