¿Cómo se configura el tamaño de fuente en una celda de texto para que la cadena llena rect de la célula?

StackOverflow https://stackoverflow.com/questions/2410401

  •  18-09-2019
  •  | 
  •  

Pregunta

Tengo una vista que contiene dos NSTextFieldCells. El tamaño en el que se dibujan estas células se deriva de la tamaño de la vista, y quiero el texto en cada celda para ser el más grande que se ajuste en el tamaño derivada de la célula. Aquí es lo que tengo, que no establece el tamaño de la fuente:

- (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];
}

Así que sé que puedo pedir una cadena de qué tamaño le tomaría a atributos dados, y sé que puedo pedir un control para cambiar su tamaño para adaptarse a su contenido. Ninguno de los que parece aplicable: Quiero el contenido (en este caso, stringValue de la célula) a medida para ajustarse a las dimensiones rect conocidos, con los atributos necesarios para lograr que el ser desconocido. ¿Cómo puedo encontrar el tamaño necesario? Asumo que no sé qué fuente voy a utilizar (porque hago).

actualización Nota: No quiero a truncado la cadena, quiero crecen o reducir de modo que todo el asunto se ajusta, con el tamaño de la letra más grande posible, en el rect proporcionada.

¿Fue útil?

Solución

Yo uso un código similar pero con un tráfico de diferentes fuentes, tamaños de hasta 10.000 y tiene en cuenta la altura disponible, así como la anchura del área de texto se muestra en.

#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;
}

Otros consejos

Se recomendó fuera de banda que intento una búsqueda binaria de un tamaño adecuado. Este es un ejemplo muy limitado de que:

- (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];
}

Las limitaciones (será mejor que no necesita un 32pt o más grande fuente, o cualquier cosa que no es Lucida Grande) no son importantes para mi necesidad, pero sin duda sería poner algunas personas con este método. Voy a dejar la pregunta abierta, y aceptar un enfoque más robusto.

Este es un método que no hace adivinar y comprobar. Dependiendo de la fuente, un poco de relleno puede ser necesario para evitar desbordamiento (sizeWithAttributes no escala a la perfección). Boom!

-(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];
}

Dos ideas. Uno lo he intentado, el otro podría trabajar:

1) Haz como en esta pregunta: ¿Cómo para truncar una NSString basado en el ancho gráfica? es decir, simplemente probar diferentes tamaños hasta que no se ajusta más

2) Crear la célula, darle la máxima rect y configurar para que se ajuste a su texto en la celda, a continuación, pedir que por su tamaño ideal (hay un método en el que hay que hace eso) a continuación, cambiar el tamaño de las células de nuevo. Por fin, si he entendido su problema correctamente.

En mi caso utilizo el siguiente:

- (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;


    }




}

Lo sentimos: han pasado cinco años. Ancho de texto ya no puede ser la preocupación primordial en su vida de vigilia. Sin embargo, tengo la respuesta; tal vez otros se beneficiarán.

La clave fundamental para exacta texto de ancho-dimensionamiento (y esto también funciona para el texto de la altura) es darse cuenta de que la anchura del texto representado por supuesto hace variar - pero linealmente! - con el ajuste del atributo font-size. No hay necesidad de búsqueda binaria o para recoger y poner a prueba a través de todos los posibles valores de los atributos de fuente de tamaño, cuando uno tiene una función lineal; uno sólo tiene que estar seguro de dos puntos de la gráfica.

Para preparar, no se abre el arco, pero calcular el ancho de la cadena de prestado en, por ejemplo, el tamaño del texto 20 y al tamaño del texto 40. Esto le da dos puntos de datos en la función lineal "prestado ancho de cadena como una función de texto de tamaño atributo". A continuación, extrapolar para adaptarse a la cadena en todo lo ancho representado actualmente necesita.

he encontrado este método para producir de manera uniforme con resultados buenos y rápidos. Con variaciones en la fuente, por supuesto, a veces es posible obtener caracteres que cuelgan dos o tres píxeles por encima del borde del cuadro de límite - pero esto es un artefacto de diseño de la fuente. Las fuentes bien diseñados va a funcionar bien, e incluso con fuentes locos, por lo general sólo tiene que proporcionar frontera del margen de unos pocos píxeles.

Estas son las rutinas que se utilizan cuando tuve este problema el mes pasado. Siéntase libre de utilizar este código.

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

//
//  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

/******************************************************************************************/
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top