Замена устаревшего размера с шрифтом: в iOS 7?

320

В iOS 7 sizeWithFont:теперь устарела. Как мне теперь передать объект UIFont в метод замены sizeWithAttributes:?

Джеймс Куанг
источник

Ответы:

521

Используйте sizeWithAttributes:вместо этого, который теперь принимает NSDictionary. Передайте пару с ключом UITextAttributeFontи вашим объектом шрифта следующим образом:

CGSize size = [string sizeWithAttributes:
    @{NSFontAttributeName: [UIFont systemFontOfSize:17.0f]}];

// Values are fractional -- you should take the ceilf to get equivalent values
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
Джеймс Куанг
источник
60
при работе с и NSStringи UILabel(не ВСЕГДА, но часто так), чтобы предотвратить дублирование кода и т. д., вы также можете заменить [UIFont systemFontOfSize:17.0f]на label.font- помогает обслуживание кода, ссылаясь на существующие данные, а не вводя их несколько раз, или ссылаясь на константы по всей место и т. д.
toblerpwn
8
@toblerpwn Возможно, метка не существует, и вы пытаетесь рассчитать теоретическую метку.
ботбот
5
Как бы я использовал этот пример, чтобы получить size.height с меткой, которая имеет фиксированную ширину, например, 250? ИЛИ, если его метка с автолигонией занимает большую часть ширины, и я перехожу к ландшафтному режиму.
Pedroinpeace
12
@Pedroinpeace Вы бы использовали boundingRectWithSize:options:attributes:context:вместо этого, передавая CGSizeMake(250.0f, CGFLOAT_MAX)в большинстве случаев.
Джеймс Куанг
9
Что еще стоит отметить sizeWithAttributes: не 100% эквивалентно. sizeWithFont используется для округления размеров до целочисленных (пиксельных) значений. Предложите использовать ceilf для результирующей высоты / ширины, иначе вы можете получить размытые артефакты (особенно не сетчатка HW), если вы используете это для позиционных вычислений.
Ник H247
172

Я считаю, что эта функция устарела, потому что эта серия NSString+UIKitфункций ( sizewithFont:...и т. Д.) Основана на UIStringDrawingбиблиотеке, которая не была поточно- ориентированной . Если вы попытаетесь запустить их не в главном потоке (как любая другая UIKitфункциональность), вы получите непредсказуемое поведение. В частности, если вы запускаете функцию одновременно на нескольких потоках, это может привести к сбою приложения. Вот почему в iOS 6 они представили boundingRectWithSize:...метод для NSAttributedString. Это было построено на вершинеNSStringDrawing библиотек и является потокобезопасным.

Если вы посмотрите на новую NSString boundingRectWithSize:...функцию, она запрашивает массив атрибутов так же, как и NSAttributeString. Если бы мне пришлось угадывать, эта новая NSStringфункция в iOS 7 является просто оболочкой дляNSAttributeString функции из iOS 6.

На этой ноте, если бы вы поддерживали только iOS 6 и iOS 7, то я определенно изменил бы все ваши NSString sizeWithFont:...на NSAttributeString boundingRectWithSize. Это избавит вас от головной боли, если у вас будет странный многопоточный угловой корпус! Вот как я перевелNSString sizeWithFont:constrainedToSize: :

Что раньше было:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font 
               constrainedToSize:(CGSize){width, CGFLOAT_MAX}];

Можно заменить на:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
CGSize size = rect.size;

Обратите внимание, что в документации упоминается:

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

Таким образом, чтобы вывести вычисленную высоту или ширину, которая будет использоваться для размеров видов, я бы использовал:

CGFloat height = ceilf(size.height);
CGFloat width  = ceilf(size.width);
Мистер Т
источник
boundingRectWithSize устарела в iOS 6.0.
Нирав
2
@Nirav Я не вижу упоминаний об устаревании. Можете ли вы указать мне, где написано, что это устарело? Спасибо! ( developer.apple.com/library/ios/documentation/uikit/reference/… )
г-н Т.
2
Может быть, @Nirav имел в виду, что он не был доступен для NSString в iOS 6 (что косвенно упоминается в ответе)?
Newenglander
1
@VagueExplanation Я только что попробовал это, и boundingRectForSize все еще не рекомендуется для NSAttributedString. Это также не говорит, что устарело в документации.
Мистер Т
5
Фантастически для упоминания использования ceilf (). Больше нигде не упоминалось об этом, и мой размер всегда был слишком маленьким. Спасибо!
Джонатан Браун
29

Как вы можете видеть sizeWithFontна сайте Apple Developer, он устарел, поэтому нам нужно использовать sizeWithAttributes.

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

NSString *text = @"Hello iOS 7.0";
if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
    // code here for iOS 5.0,6.0 and so on
    CGSize fontSize = [text sizeWithFont:[UIFont fontWithName:@"Helvetica" 
                                                         size:12]];
} else {
    // code here for iOS 7.0
   CGSize fontSize = [text sizeWithAttributes: 
                            @{NSFontAttributeName: 
                              [UIFont fontWithName:@"Helvetica" size:12]}];
}
Ayush
источник
17
В этом случае лучше использовать [NSObject respondsToSelector:]метод, как здесь: stackoverflow.com/a/3863039/1226304
derpoliuk
16

Я создал категорию для решения этой проблемы, вот она:

#import "NSString+StringSizeWithFont.h"

@implementation NSString (StringSizeWithFont)

- (CGSize) sizeWithMyFont:(UIFont *)fontToUse
{
    if ([self respondsToSelector:@selector(sizeWithAttributes:)])
    {
        NSDictionary* attribs = @{NSFontAttributeName:fontToUse};
        return ([self sizeWithAttributes:attribs]);
    }
    return ([self sizeWithFont:fontToUse]);
}

Таким образом , у вас есть только найти / заменить sizeWithFont:с , sizeWithMyFont:и вы хорошо идти.

ПЗУ.
источник
1
хотя это вызовет предупреждение компилятора на ios7 + для ссылки на sizeWithFont или предупреждение / ошибку компиляции на <ios7 для ссылки на sizeWithAttributes! Вероятно, лучше использовать макрос вместо проверки отклика на отклик - при необходимости. Но поскольку вы больше не можете доставлять в Apple с ioS6 SDK ... это, вероятно, не так!
Ник H247
Добавьте это к предупреждению подавления #pragma Диагностика GCC игнорируется "-Wdeprecated-декларации"
Райан Хейтнер
10

В iOS7 мне нужно было, чтобы логика возвращала правильную высоту для метода tableview: heightForRowAtIndexPath, но sizeWithAttributes всегда возвращает одну и ту же высоту независимо от длины строки, потому что не знает, что она будет помещена в ячейку таблицы фиксированной ширины. , Я нашел, что это прекрасно работает для меня и вычисляет правильную высоту, принимая во внимание ширину ячейки таблицы! Это основано на ответе г-на Т. выше.

NSString *text = @"The text that I want to wrap in a table cell."

CGFloat width = tableView.frame.size.width - 15 - 30 - 15;  //tableView width - left border width - accessory indicator - right border width
UIFont *font = [UIFont systemFontOfSize:17];
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
CGSize size = rect.size;
size.height = ceilf(size.height);
size.width  = ceilf(size.width);
return size.height + 15;  //Add a little more padding for big thumbs and the detailText label
user3055587
источник
7

Многострочные метки, использующие динамическую высоту, могут требовать дополнительной информации для правильной установки размера. Вы можете использовать sizeWithAttributes с UIFont и NSParagraphStyle, чтобы указать как шрифт, так и режим перевода строки.

Вы должны определить стиль абзаца и использовать NSDictionary следующим образом:

// set paragraph style
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByWordWrapping];
// make dictionary of attributes with paragraph style
NSDictionary *sizeAttributes        = @{NSFontAttributeName:myLabel.font, NSParagraphStyleAttributeName: style};
// get the CGSize
CGSize adjustedSize = CGSizeMake(label.frame.size.width, CGFLOAT_MAX);

// alternatively you can also get a CGRect to determine height
CGRect rect = [myLabel.text boundingRectWithSize:adjustedSize
                                                         options:NSStringDrawingUsesLineFragmentOrigin
                                                      attributes:sizeAttributes
                                                         context:nil];

Вы можете использовать CGSize 'AdjustSize' или CGRect как свойство rect.size.height, если вы ищете высоту.

Дополнительная информация о NSParagraphStyle здесь: https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSParagraphStyle_Class/Reference/Reference.html

bitsand
источник
1
Кажется, что есть проблема с синтаксисом сразу после AdjustSize.
Крис Принс
Спасибо, что
поймали
6
// max size constraint
CGSize maximumLabelSize = CGSizeMake(184, FLT_MAX)

// font
UIFont *font = [UIFont fontWithName:TRADE_GOTHIC_REGULAR size:20.0f];

// set paragraph style
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;

// dictionary of attributes
NSDictionary *attributes = @{NSFontAttributeName:font,
                             NSParagraphStyleAttributeName: paragraphStyle.copy};

CGRect textRect = [string boundingRectWithSize: maximumLabelSize
                                     options:NSStringDrawingUsesLineFragmentOrigin
                                  attributes:attributes
                                     context:nil];

CGSize expectedLabelSize = CGSizeMake(ceil(textRect.size.width), ceil(textRect.size.height));
Кирит Вагела
источник
1
Благодаря Кириту, это избавило меня от многих часов головной боли, определение атрибутов и словарей до блока CGRect было тем, что делало это для меня ... также намного легче читать.
Азин Мерноуш
3

Создайте функцию, которая принимает экземпляр UILabel. и возвращает CGSize

CGSize constraint = CGSizeMake(label.frame.size.width , 2000.0);
// Adjust according to requirement

CGSize size;
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0){

    NSRange range = NSMakeRange(0, [label.attributedText length]);

    NSDictionary *attributes = [label.attributedText attributesAtIndex:0 effectiveRange:&range];
    CGSize boundingBox = [label.text boundingRectWithSize:constraint options: NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;

    size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
}
else{
    size = [label.text sizeWithFont:label.font constrainedToSize:constraint lineBreakMode:label.lineBreakMode];
}

return size;
Мухаммед Идрис
источник
В моем случае для динамической высоты строки я полностью удалил метод HeightForRow и использовал UITableViewAutomaticDimension. tableView.estimatedRowHeight = 68.0 tableView.rowHeight = UITableViewAutomaticDimension
Евгений Брагинец
3

Альтернативное решение-

CGSize expectedLabelSize;
if ([subTitle respondsToSelector:@selector(sizeWithAttributes:)])
{
    expectedLabelSize = [subTitle sizeWithAttributes:@{NSFontAttributeName:subTitleLabel.font}];
}else{
    expectedLabelSize = [subTitle sizeWithFont:subTitleLabel.font constrainedToSize:subTitleLabel.frame.size lineBreakMode:NSLineBreakByWordWrapping];
}
Смельчак
источник
1
Это выдаст предупреждение компилятора на iOS6 и 7. И будет иметь совершенно разное поведение для каждого!
Ник Н247
3

Основываясь на @bitsand, это новый метод, который я только что добавил в свою категорию NSString + Extras:

- (CGRect) boundingRectWithFont:(UIFont *) font constrainedToSize:(CGSize) constraintSize lineBreakMode:(NSLineBreakMode) lineBreakMode;
{
    // set paragraph style
    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [style setLineBreakMode:lineBreakMode];

    // make dictionary of attributes with paragraph style
    NSDictionary *sizeAttributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName: style};

    CGRect frame = [self boundingRectWithSize:constraintSize options:NSStringDrawingUsesLineFragmentOrigin attributes:sizeAttributes context:nil];

    /*
    // OLD
    CGSize stringSize = [self sizeWithFont:font
                              constrainedToSize:constraintSize
                                  lineBreakMode:lineBreakMode];
    // OLD
    */

    return frame;
}

Я просто использую размер полученного кадра.

Крис Принс
источник
2

Вы все еще можете использовать sizeWithFont. но в iOS> = 7.0 метод вызывает сбой, если строка содержит начальные и конечные пробелы или конечные строки \n.

Обрезка текста перед его использованием

label.text = [label.text stringByTrimmingCharactersInSet:
             [NSCharacterSet whitespaceAndNewlineCharacterSet]];

Это также может относиться к sizeWithAttributesи [label sizeToFit].

также, когда у вас есть nsstringdrawingtextstorage message sent to deallocated instanceустройство iOS 7.0, оно имеет дело с этим

Hasan
источник
2

Лучше использовать автоматические размеры (Swift):

  tableView.estimatedRowHeight = 68.0
  tableView.rowHeight = UITableViewAutomaticDimension

NB: 1. Прототип UITableViewCell должен быть правильно спроектирован (для примера не забудьте установить UILabel.numberOfLines = 0 и т. Д.) 2. Удалить метод HeightForRowAtIndexPath

введите описание изображения здесь

ВИДЕО: https://youtu.be/Sz3XfCsSb6k

Евгений Брагинец
источник
Это берет статическую высоту ячейки, которая определена в ячейке прототипа раскадровки
Кирит Вагела
Добавил эти две строки и убедился, что моя UILabel установлена ​​на 0 строк. Не работает, как рекламируется. Что еще вы сделали, чтобы заставить работать только эти две строки кода?
Уилл
1
хм, также вы должны прикрепить ограничения на
Евгений Брагинец,
1
Нет, этого должно быть достаточно. Есть ли у вас ограничения для автоматического размещения ярлыка, прикрепленного к суперпредставлению?
Евгений Брагинец
1

Принятый ответ в Xamarin будет (используйте sizeWithAttributes и UITextAttributeFont):

        UIStringAttributes attributes = new UIStringAttributes
        { 
            Font = UIFont.SystemFontOfSize(17) 
        }; 
        var size = text.GetSizeUsingAttributes(attributes);
Алекс Сороколетов
источник
1

Как ответ @Ayush:

Как вы можете видеть sizeWithFontна сайте Apple Developer, он устарел, поэтому нам нужно использовать sizeWithAttributes.

Что ж, предположим, что в 2019 году вы, вероятно, используете Swift, а Stringне Objective-c, и NSStringвот правильный способ получить размер a Stringс предопределенным шрифтом:

let stringSize = NSString(string: label.text!).size(withAttributes: [.font : UIFont(name: "OpenSans-Regular", size: 15)!])
Ромуло Б.М.
источник
Как вы могли бы использовать это, чтобы определить размер шрифта?
Джозеф Астрахань
@JosephAstrahan это не для определения размера шрифта, это для получения размера (CGSize) строки с определенным шрифтом. Если вы хотите получить размер шрифта надписи, вы можете легко использовать label.font
Romulo BM
используя вашу идею, я создал способ более или менее увеличить размер шрифта ( stackoverflow.com/questions/61651614/… ), label.font не будет работать в моем случае из-за некоторых сложных проблем, связанных с кликабельностью ярлыков, только если Точный шрифт известен, вы можете прочитать об этом в моем посте.
Иосиф Астрахань
0
- (CGSize) sizeWithMyFont:(UIFont *)fontToUse
{
    if ([self respondsToSelector:@selector(sizeWithAttributes:)])
    {
        NSDictionary* attribs = @{NSFontAttributeName:fontToUse};
        return ([self sizeWithAttributes:attribs]);
    }
    return ([self sizeWithFont:fontToUse]);
}
Просенджит Госвами
источник
0

Вот эквивалент monotouch, если кому-то это нужно:

/// <summary>
/// Measures the height of the string for the given width.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="width">The width.</param>
/// <param name="padding">The padding.</param>
/// <returns></returns>
public static float MeasureStringHeightForWidth(this string text, UIFont font, float width, float padding = 20)
{
    NSAttributedString attributedString = new NSAttributedString(text, new UIStringAttributes() { Font = font });
    RectangleF rect = attributedString.GetBoundingRect(new SizeF(width, float.MaxValue), NSStringDrawingOptions.UsesLineFragmentOrigin, null);
    return rect.Height + padding;
}

который можно использовать так:

public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
    //Elements is a string array
    return Elements[indexPath.Row].MeasureStringHeightForWidth(UIFont.SystemFontOfSize(UIFont.LabelFontSize), tableView.Frame.Size.Width - 15 - 30 - 15);
}
Рузвельт
источник
0
CGSize maximumLabelSize = CGSizeMake(label.frame.size.width, FLT_MAX);
CGSize expectedLabelSize = [label sizeThatFits:maximumLabelSize];
float heightUse = expectedLabelSize.height;
user1802778
источник
0

Попробуйте этот синтаксис:

NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];
Муруганандам Сатхасивам
источник
-1

В ios 7 у меня ничего не получалось. Вот что я в итоге сделал. Я помещаю это в свой пользовательский класс ячеек и вызываю метод в моем методе heightForCellAtIndexPath.

Моя ячейка похожа на ячейку описания при просмотре приложения в магазине приложений.

Сначала в раскадровке установите свой ярлык на «attributeText», установите количество строк равным 0 (это автоматически изменит размер ярлыка (только для ios 6+)) и установите для него перенос слов.

Затем я просто складываю все высоты содержимого ячейки в моем пользовательском классе ячеек. В моем случае у меня есть Метка сверху, которая всегда говорит «Описание» (_descriptionHeadingLabel), меньшая метка, которая имеет переменный размер, содержит фактическое описание (_descriptionLabel) ограничение от верхней части ячейки до заголовка (_descriptionHeadingLabelTopConstraint) , Я также добавил 3, чтобы немного растянуть дно (примерно такое же количество яблок в ячейке типа субтитров).

- (CGFloat)calculateHeight
{
    CGFloat width = _descriptionLabel.frame.size.width;
    NSAttributedString *attributedText = _descriptionLabel.attributedText;
    CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX} options: NSStringDrawingUsesLineFragmentOrigin context:nil];

    return rect.size.height + _descriptionHeadingLabel.frame.size.height + _descriptionHeadingLabelTopConstraint.constant + 3;
}

И в моем представлении Table View:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
    if (indexPath.row == 0) {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"descriptionCell"];
        DescriptionCell *descriptionCell = (DescriptionCell *)cell;
        NSString *text = [_event objectForKey:@"description"];
        descriptionCell.descriptionLabel.text = text;

        return [descriptionCell calculateHeight];
    }

    return 44.0f;
}

Вы можете изменить оператор if, чтобы он стал немного «умнее» и фактически получил идентификатор ячейки из какого-либо источника данных. В моем случае ячейки будут жестко закодированы, поскольку их будет определенное количество в определенном порядке.

Kubee
источник
boundingRectWithSizeв ios 9.2 присутствуют проблемы, отличающиеся результатами от ios <9.2. Вы нашли или знаете любой другой лучший способ сделать это.
jose920405