Размер шрифта NSString зависит от ширины кадра

Я использую drawRect для отображения текста, вызова NSString, Я пытаюсь реализовать с помощью sizeWithFont автоматическое изменение размера шрифта (сокращение) с размером шрифта по умолчанию 17 и использование цикла для уменьшения размера шрифта на 1, если он не соответствует размеру ширины. Может кто-нибудь помочь мне, как реализовать это? Пример был бы хорош сейчас у меня просто размер шрифта установлен на 17,0

[[self.string displayName] drawAtPoint:CGPointMake(xcoord, ycoord) withFont:[UIFont boldSystemFontOfSize:17.0]];
CGSize size = [[self.patient displayName] sizeWithFont:[UIFont boldSystemFontOfSize:17.0]];
max_current_y = size.height > max_current_y ? size.height : max_current_y;
xcoord = xcoord + 3.0f + size.width;

7 ответов

Решение

ОК, не бери в голову. Вот модифицированная версия того же метода, который принимает NSString, для которого нужно вернуть шрифт:

    -(UIFont*)getFontForString:(NSString*)string
               toFitInRect:(CGRect)rect
                  seedFont:(UIFont*)seedFont{
    UIFont* returnFont = seedFont;
    CGSize stringSize = [string sizeWithAttributes:@{NSFontAttributeName : seedFont}];

    while(stringSize.width > rect.size.width){
        returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
        stringSize = [string sizeWithAttributes:@{NSFontAttributeName : returnFont}];
    }

    return returnFont;
}

Вот как это назвать:

NSString* stringToDraw = @"Test 123";

    CGRect rect = CGRectMake(100., 100., 100., 200.);
    UIFont* font = [self getFontForString:stringToDraw toFitInRect:rect seedFont:[UIFont systemFontOfSize:20]];
    [stringToDraw drawInRect:rect withFont:font];

Код для iOS7+

Попытка размеров шрифта с шагом 1.0 может быть очень медленной. Вы можете значительно улучшить алгоритм, сделав две меры для двух разных размеров, а затем используя линейную аппроксимацию, чтобы угадать размер, который будет очень близок к правильному.

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

// any values will do, prefer those near expected min and max
CGFloat size1 = 12.0, size2 = 56.0; 
CGFloat width1 = measure_for_size(size1);
CGFloat width2 = measure_for_size(size2);

while (1) {
    CGFloat guessed_size = size1 + (required_width - width1) * (size2 - size1) / (width2 - width1);

    width2 = measure_for_size(guessed_size);
    if ( fabs(guessed_size-size2) < some_epsilon || !is_close_enough(width2, required_width) ) {
        size2 = guessed_size;
        continue;
    }
    // round down to integer and clamp guessed_size as appropriate for your design
    return floor(clamp(guessed_size, 6.0, 24.0));
}

is_close_enough() реализация полностью зависит от вас. Учитывая, что ширина текста увеличивается почти линейно от размера шрифта, вы можете просто отбросить его и сделать 2-4 итерации, которых должно быть достаточно.

Я хотел попробовать сделать версию, в которой не нужно многократно проверять размеры шрифта, используя цикл do... while. Вместо этого я предположил, что размеры точек шрифта были линейным масштабом, затем определил разницу в размерах между требуемой шириной кадра и фактической шириной кадра, а затем соответствующим образом скорректировал размер шрифта. Поэтому я в конечном итоге с этой функцией:

+ (CGFloat)fontSizeToFitString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
    UILabel *label = [UILabel new];
    label.font = font;
    label.text = string;
    [label sizeToFit];

    float ratio = width / label.frame.size.width;
    return font.pointSize * ratio;
}

Передайте шрифт любого размера, а также строку и требуемую ширину, и он вернет вам размер точки для этого шрифта.

Я также хотел бы пойти немного дальше и выяснить размер шрифта для многострочной строки, чтобы самая длинная строка подходила без разрыва строки:

+ (CGFloat)fontSizeToFitLongestLineOfString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
    NSArray *stringLines = [string componentsSeparatedByString:@"\n"];

    UILabel *label = [UILabel new];
    label.font = font;

    float maxWidth = 0;

    for(NSString *line in stringLines)
    {
        label.text = line;
        [label sizeToFit];
        maxWidth = MAX(maxWidth, label.frame.size.width);
    }

    float ratio = width / maxWidth;
    return font.pointSize * ratio;
}

Кажется, работает отлично для меня. Надеюсь, это поможет кому-то еще.

Оригинальный плакат не указывал, на какой платформе он работал, но для разработчиков OSX на Mavericks, sizeWithFont: не существует, и нужно использовать sizeWithAttributes:

NSSize newSize = [aString sizeWithAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
           [NSFont fontWithName:@"Arial Rounded MT Bold" size:53.0],NSFontAttributeName,nil
]];

Я немного изменил решение @puru020, добавил поддержку атрибутов и немного улучшил:

Примечание. Метод должен быть включен в категорию NSString.

- (UIFont*)requiredFontToFitInSize:(CGSize)size seedFont:(UIFont*)seedFont attributes:(NSDictionary*)attributes{
   UIFont *returnFont = [UIFont systemFontOfSize:seedFont.pointSize +1];
   NSMutableDictionary *mutableAttributes = attributes.mutableCopy;
   CGSize stringSize;

   do {
    returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
    [mutableAttributes setObject:returnFont forKey:NSFontAttributeName];
    stringSize = [self sizeWithAttributes:mutableAttributes];
   } while (stringSize.width > size.width);

   return returnFont;
}

Вот еще один метод, основанный на ответах @puru020 & @jowie. Надеюсь, это поможет кому-то

-(UIFont *) adjustedFontSizeForString:(NSString *)string forWidth:(float)originalWidth forFont:(UIFont *)font
{
    CGSize stringSize = [string sizeWithFont:font];
    if(stringSize.width <= originalWidth)
    {
        return font;
    }
    float ratio = originalWidth / stringSize.width;
    float fontSize = font.pointSize * ratio;
    return [font fontWithSize:fontSize];
}

Вот метод, который может вернуть вам шрифт, который поместится в прямоугольнике:

-(UIFont*)getFontToFitInRect:(CGRect)rect seedFont:(UIFont*)seedFont{
    UIFont* returnFont = seedFont;
    CGSize stringSize = [self sizeWithFont:returnFont];

    while(stringSize.width > rect.size.width){
        returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
        stringSize = [self sizeWithFont:returnFont];
    }

    return returnFont;
}

Вы можете добавить этот метод в категорию NSString. Подробнее о том, как добавить категорию, можно узнать здесь: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210 -CH6-SW2

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

Другие вопросы по тегам