Масштабирование текста для размещения на iPhone - PullRequest
8 голосов
/ 17 ноября 2010

У меня возникли проблемы с разработкой «наилучшего» способа визуализации текста в моем приложении.

Мой основной вид состоит из текстового представления, а дизайн приложения диктует несколько вещей:

  • Размер (шрифт) текста должен быть динамическим
  • Текстовый фрейм должен быть центрирован по вертикали в представлении
  • Перенос слов должен быть автоматическим и только при необходимости (по возможности избегать)

В настоящее время я использую UILabel и следующий код, чтобы попытаться угадать наилучший размер шрифта для объема текста:

txt  = @"this is just some sample text";

mylabel.font = [self getFontForString:txt];
mylabel.adjustsFontSizeToFitWidth = YES;
mylabel.numberOfLines = 0;
[mylabel setText:txt];

И

- (UIFont *) getFontForString:(NSString *)txt {
     CGFloat                textLength = txt.length;
     CGFloat                maxFontSize = 71;
     CGFloat                minFontSize = 27;
     CGFloat                newFontSize = 0;

     NSArray                *chunks = [txt componentsSeparatedByString:@" "];
     NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"length" ascending:NO] autorelease];
     NSArray                *sortedChunks = [chunks sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

     CGSize                labelSize = theThingLabel.bounds.size;
     CGSize                projectedSize = [[sortedChunks objectAtIndex:0] sizeWithFont:[UIFont boldSystemFontOfSize:maxFontSize]];

     if (projectedSize.width > labelSize.width) {
          CGFloat percentageDifference = ((projectedSize.width - labelSize.width)/labelSize.width)*100;
          if (percentageDifference > 50) {
               newFontSize = ((minFontSize/percentageDifference)*100) - 10;
               if (newFontSize < minFontSize) newFontSize = minFontSize;
          } else {
               newFontSize = ((percentageDifference/maxFontSize)*100) - 10;
               if(newFontSize < (maxFontSize/2)) newFontSize = maxFontSize - abs(newFontSize);
          }
     } else {
          if ( textLength > 11 && textLength < 255) {
               newFontSize = (maxFontSize - ((maxFontSize - minFontSize) * ((textLength- 11) / 100)));
          } else if (textLength <= 11) {
               newFontSize = maxFontSize;
          } else if (textLength >= 255) {
               newFontSize = minFontSize;
          }
     }

     return [UIFont boldSystemFontOfSize:newFontSize];
}

Это работает, в некоторой степени, но часто падает, когда текст немного длинен, эти два примера показывают, что он отображает следующие строки:

  • "короткое количество текста"
  • "значительно больше текста, который я все еще хочу отрисовать."

short text longer text

Как вы можете видеть во втором примере (с более длинным текстом), существует ряд проблем:

  • Начальная вдова
  • упал у
  • Пропавшее "красиво".

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

Ответы [ 3 ]

3 голосов
/ 02 декабря 2010

Следующий метод будет использован full для получения размера шрифта для конкретной строки для определенного прямоугольника (области).

-(float) maxFontSizeThatFitsForString:(NSString*)_string inRect:(CGRect)rect withFont:(NSString *)fontName onDevice:(int)device {   
// this is the maximum size font that will fit on the device
float _fontSize = maxFontSize;
float widthTweak;

// how much to change the font each iteration. smaller
// numbers will come closer to an exact match at the 
// expense of increasing the number of iterations.
float fontDelta = 2.0;

// sometimes sizeWithFont will break up a word 
// if the tweak is not applied. also note that 
// this should probably take into account the
// font being used -- some fonts work better
// than others using sizeWithFont.
if(device == IPAD)
    widthTweak = 0.2;
else
    widthTweak = 0.2;

CGSize tallerSize = CGSizeMake(rect.size.width-(rect.size.width*widthTweak), 100000);
CGSize stringSize = CGSizeZero;

    if([[UIDevice currentDevice].systemVersion floatValue]>=7.0){
        NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:[UIFont boldSystemFontOfSize:17] forKey: NSFontAttributeName];
        stringSize = [_string boundingRectWithSize: tallerSize options:NSStringDrawingUsesLineFragmentOrigin attributes:stringAttributes context:nil].size;
    }
    else{
        stringSize = [_string sizeWithFont:[UIFont fontWithName:fontName size:_fontSize] constrainedToSize:tallerSize];
    }

while (stringSize.height >= rect.size.height)
{       
    _fontSize -= fontDelta;
    stringSize = [_string sizeWithFont:[UIFont fontWithName:fontName size:_fontSize] constrainedToSize:tallerSize];
}

return _fontSize;
}

Используйте это, получите размер шрифта и назначьте метку.

2 голосов
/ 17 ноября 2010

Одна вещь, которую я нашел полезной, - это небольшая функция, которая принимает NSString, UIFont и CGSize, возвращая CGFloat, представляющий наибольший размер шрифта для этой строки, который будет помещен в переданный CGSize - он последовательно использует sizeWithFont меньшие размеры точек, пока возвращаемый размер не вписывается в аргумент CGSize. Вы можете передать CGFLOAT_MAX как x или y, если вам не важно одно измерение, например, когда вы проверяете ширину линии и будете проверять высоту позже для всей строки. Конечно, вы определяете максимальный и минимальный размер шрифта.

Я бы начал с разделения строки на массив слов, используя componentsSeparatedByString. Возможно, вы хотите определить слово, которое может быть дефисировано как слово, кратное следующему по величине слову, когда все слова отображаются с одинаковым размером шрифта, поэтому вы создаете этот массив для произвольного размера шрифта и у вас есть соответствующий массив относительной ширины (или, может быть, словарь, где слово является ключом, а ширина - значением). Вам нужно отсканировать и разделить любое такое слово (слова) на два слова (одно из которых содержит дефис), прежде чем продолжить. Затем вам нужно найти размер шрифта, при котором все слова вписываются в ограничение размера, в примере, который вы показали, что это ширина, используя функцию, упомянутую выше.

Когда потенциальный размер шрифта известен, вы проверяете другие свои ограничения, такие как вдовы - возможно, вы определяете их с точки зрения позиций, которых они могут не иметь (начало для одного), и доли ширины линии, которая делает вдову. Если вы обнаружите проблему, вы объедините слова в массиве, чтобы удалить вдову и пересчитать новый размер шрифта-кандидата - это может привести к тому, что в результате вы получите дефис в первой строке или шрифт меньшего размера, но если ваш текст «непропорционально» долго начинать с группы маленьких слов "тогда у вас может не быть выбора.

Проблема «красивого» исчезновения не так сложна, вам просто нужно перепроверить высоту отображаемого текста - возможно, вы могли бы использовать sizeWithFont:constrainedToSize после того, как завершите описанные выше процедуры в качестве окончательной проверки. Если это не удается, уменьшите максимальный размер шрифта кандидата и начните сначала.

Итак:

candidateFontSize = CGFLOAT_MAX;
while(stillRendering)

  break string into array of words
  make array of word widths
  check for and divide hyphenation candidates

  for each word
    if max font size returned for rendered word in size constraint < canddiateFontSize
      candidateFontSize = max font size returned

  for each word
    measure each word and record rendered size in candidateFontSize

  stillRendering = NO;

  for each word 
    check each word for problems such as widows and solve
    if problem found
      stillRendering = YES;

  check entire string using sizeWithFont:constrainedToSize using width, CGFLOAT_MAX

  if height is too large
    stillRendering = YES;
    candidateFontSize--;

Это только начало, но оно должно быть работоспособным.

0 голосов
/ 23 ноября 2010
CGSize expectedLabelSize = [yourString sizeWithFont:yourLable.font];

        //adjust the label the the new height.
    CGRect newFrame = yourLable.frame;
    newFrame.size.width = expectedLabelSize.width;
    newFrame.origin.x = x;
    yourLable.frame = newFrame;
...