Substituição do tamanho obsoleto - com iOS 7?


320

No iOS 7, sizeWithFont:agora está obsoleto. Como agora passo o objeto UIFont para o método de substituição sizeWithAttributes:?

Respostas:


521

Use em sizeWithAttributes:vez disso, que agora leva um NSDictionary. Passe o par com a chave UITextAttributeFonte seu objeto de fonte assim:

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
ao trabalhar com um NSStringe um UILabel(não SEMPRE o caso, mas muitas vezes sim), para evitar código duplicado / etc, você também pode substituir [UIFont systemFontOfSize:17.0f]por label.font- ajuda na manutenção do código fazendo referência a dados existentes, digitando-o várias vezes ou fazendo referência a constantes em todo o local, etc
toblerpwn

8
@toblerpwn é possível que o rótulo não exista e você esteja tentando calcular um rótulo teórico.
Botbot

5
Como eu usaria este exemplo para obter o size.height com uma etiqueta que tenha uma largura fixa de, por exemplo, 250? Ou se é um rótulo com autolatyout witch que ocupa um procentage da largura e eu vou para o modo paisagístico.
Pedroinpeace

12
@Pedroinpeace Você usaria boundingRectWithSize:options:attributes:context:, passando CGSizeMake(250.0f, CGFLOAT_MAX)na maioria dos casos.
James Kuang

9
Outra coisa a notar sizeWithAttributes: não é 100% equivalente. sizeWithFont usado para arredondar os tamanhos para valores inteiros (pixels). Sugira o uso de ceilf na altura / largura resultante ou poderá ocorrer artefatos embaçados (especialmente a retina não HW) se você o usar para cálculos posicionais.
Nick H247

172

Acredito que a função foi descontinuada porque essa série de NSString+UIKitfunções ( sizewithFont:..., etc) se baseava na UIStringDrawingbiblioteca, o que não era seguro para threads. Se você tentou executá-los não no segmento principal (como qualquer outra UIKitfuncionalidade), obterá comportamentos imprevisíveis. Em particular, se você executou a função em vários segmentos simultaneamente, provavelmente o aplicativo falhará. É por isso que no iOS 6, eles introduziram o boundingRectWithSize:...método para NSAttributedString. Isso foi criado no topo das NSStringDrawingbibliotecas e é seguro para threads.

Se você observar a nova NSString boundingRectWithSize:...função, ela solicitará uma matriz de atributos da mesma maneira que a NSAttributeString. Se eu tivesse que adivinhar, essa nova NSStringfunção no iOS 7 é apenas um invólucro para a NSAttributeStringfunção do iOS 6.

Nessa nota, se você estivesse suportando apenas o iOS 6 e o ​​iOS 7, eu definitivamente mudaria todo o seu NSString sizeWithFont:...para o NSAttributeString boundingRectWithSize. Isso poupará muita dor de cabeça se você tiver uma caixa de esquina multi-threading estranha! Aqui está como eu converti NSString sizeWithFont:constrainedToSize::

O que costumava ser:

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

Pode ser substituído por:

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;

Observe a documentação mencionada:

No iOS 7 e posterior, esse método retorna tamanhos fracionários (no componente de tamanho dos retornados CGRect); Para usar um tamanho retornado para visualizações de tamanho, você deve aumentar seu valor para o número inteiro mais alto mais próximo usando a função ceil.

Portanto, para extrair a altura ou largura calculada a ser usada para as vistas de dimensionamento, eu usaria:

CGFloat height = ceilf(size.height);
CGFloat width  = ceilf(size.width);

boundingRectWithSize foi descontinuado no iOS 6.0.
Nirav 02/10/2013

2
@Navav Não vejo nenhuma menção à depreciação. Você pode me indicar onde está obsoleto? Obrigado! ( developer.apple.com/library/ios/documentation/uikit/reference/… )
Sr. T

2
Talvez o @Nirav tenha significado que ele não estava disponível para o NSString no iOS 6 (mencionado indiretamente na resposta)?
newenglander

1
@VagueExplanation Acabei de tentar isso e boundingRectForSize ainda não está obsoleto para NSAttributedString. Também não diz obsoleto na documentação.
Sr. T

5
Fantástico para mencionar usando ceilf (). Em nenhum outro lugar mencionou isso e meu tamanho sempre foi um pouco pequeno demais. Obrigado!
Jonathan Brown

29

Como você pode ver sizeWithFontno site da Apple Developer, ele está obsoleto, por isso precisamos usá-lo 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]}];
}

17
Neste caso, melhor usar [NSObject respondsToSelector:]método como aqui: stackoverflow.com/a/3863039/1226304
derpoliuk

16

Eu criei uma categoria para lidar com esse problema, aqui está:

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

Desta forma, você só tem que encontrar / substituir sizeWithFont:com sizeWithMyFont:e você está pronto para ir.


1
embora isso produza um aviso do compilador no ios7 + para fazer referência a sizeWithFont ou um aviso / erro de compilação no <ios7 para fazer referência a sizeWithAttributes! Provavelmente, é melhor usar uma macro em vez de verificar responsToSelector - se necessário. Mas como você não pode mais entregar à Apple com o ioS6 SDK ... provavelmente não é!
Nick H247

Adicione isso para suprimir o aviso #pragma Diagnóstico do GCC ignorado "-Wdeprecated-declations"
Ryan Heitner

10

No iOS7, eu precisava da lógica para retornar a altura correta para o método tableview: heightForRowAtIndexPath, mas o sizeWithAttributes sempre retorna a mesma altura, independentemente do comprimento da string, porque ele não sabe que será colocado em uma célula da tabela de largura fixa . Achei isso ótimo para mim e calcula a altura correta, levando em consideração a largura da célula da tabela! Isso se baseia na resposta do Sr. T acima.

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

7

Etiquetas com várias linhas que usam altura dinâmica podem exigir informações adicionais para definir o tamanho corretamente. Você pode usar sizeWithAttributes com UIFont e NSParagraphStyle para especificar a fonte e o modo de quebra de linha.

Você definiria o estilo de parágrafo e usaria um NSDictionary como este:

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

Você pode usar a propriedade CGSize 'ajustadoSize' ou CGRect como ret.size.height se estiver procurando a altura.

Mais informações sobre o NSParagraphStyle aqui: https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSParagraphStyle_Class/Reference/Reference.html


1
Parece que há um problema de sintaxe imediatamente após o ajuste do tamanho.
Chris Prince

Obrigado por capturar esse problema de sintaxe, Chris
bitsand 19/10/2015

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
Isso me salvou horas de dores de cabeça, graças a Kirit, definir os atributos e dicionários anteriores ao bloco CGRect foi o que fez por mim ... também muito mais fácil de ler.
Azin Mehrnoosh

3

Crie uma função que utilize uma instância UILabel. e retorna 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;

No meu caso, para a altura da linha de maneira dinâmica, removi completamente o método HeightForRow e usei UITableViewAutomaticDimension. tableView.estimatedRowHeight = 68.0 tableView.rowHeight = UITableViewAutomaticDimension
Eugene Braginets

3

Solução alternativa

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
Isso produzirá um aviso do compilador no iOS6 e 7. E terá um comportamento bem diferente para cada um!
Nick H247

3

Com base no @bitsand, este é um novo método que acabei de adicionar à minha categoria 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;
}

Eu apenas uso o tamanho do quadro resultante.


2

Você ainda pode usar sizeWithFont. mas, no iOS> = 7.0, o método causa falha se a string contiver espaços à esquerda e à direita ou linhas finais \n.

Aparar texto antes de usá-lo

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

Isso também pode ser aplicado a sizeWithAttributese [label sizeToFit].

Além disso, sempre que você tiver um nsstringdrawingtextstorage message sent to deallocated instancedispositivo iOS 7.0, ele lida com isso.


2

Melhor usar as dimensões automáticas (Swift):

  tableView.estimatedRowHeight = 68.0
  tableView.rowHeight = UITableViewAutomaticDimension

NB: 1. O protótipo UITableViewCell deve ser projetado corretamente (para a instância, não esqueça de definir UILabel.numberOfLines = 0 etc) 2. Remova o método HeightForRowAtIndexPath

insira a descrição da imagem aqui

VÍDEO: https://youtu.be/Sz3XfCsSb6k


É tomar altura da célula estática que são definir na célula protótipo storyboard
Kirit Vaghela

Adicionadas essas duas linhas e verifique se meu UILabel foi definido como 0 número de linhas. Não funcionou como anunciado. O que mais você fez para que funcionasse apenas com essas duas linhas de código?
Will

1
hm, você também tem que fixar suas limitações de etiqueta para o topo e fundo da célula
Eugene Braginets

1
Não, isso deve ser suficiente. Você tem restrições de layout automático para o marcador fixado na superview?
Eugene Braginets

1
boundingRectWithSize:options:attributes:context:

1

A resposta aceita no Xamarin seria (use sizeWithAttributes e UITextAttributeFont):

        UIStringAttributes attributes = new UIStringAttributes
        { 
            Font = UIFont.SystemFontOfSize(17) 
        }; 
        var size = text.GetSizeUsingAttributes(attributes);

1

Como resposta da @Ayush:

Como você pode ver sizeWithFontno site da Apple Developer, ele está obsoleto, por isso precisamos usá-lo sizeWithAttributes.

Bem, supondo que em 2019+ você provavelmente esteja usando Swift e, em Stringvez de Objective-c NSString, e aqui está a maneira correta de obter o tamanho de a Stringcom fonte predefinida:

let stringSize = NSString(string: label.text!).size(withAttributes: [.font : UIFont(name: "OpenSans-Regular", size: 15)!])

Como você pode usar isso para determinar o tamanho da fonte?
Joseph Astrahan

@JosephAstrahan isso não é para determinar o tamanho da fonte, é para obter o tamanho (CGSize) de uma string com uma fonte determinada. Se você deseja obter o tamanho da fonte de uma etiqueta, pode usá-la com facilidade. Label.font
Romulo BM

usando sua ideia, criei uma maneira de obter o tamanho da fonte mais ou menos ( stackoverflow.com/questions/61651614/… ), label.font não funcionaria no meu caso devido a alguns problemas complicados com rótulos sendo clicáveis ​​somente se o fonte exata é conhecida, você pode ler sobre isso no meu post.
Joseph Astrahan

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

0

Aqui está o equivalente ao monotouch, se alguém precisar:

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

que pode ser usado assim:

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;

0

Experimente esta sintaxe:

NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];

-1

Nada disso funcionou para mim no iOS 7. Aqui está o que eu acabei fazendo. Coloquei isso na minha classe de célula personalizada e chamo o método no meu método heightForCellAtIndexPath.

Meu celular é semelhante à célula de descrição ao visualizar um aplicativo na loja de aplicativos.

Primeiro no storyboard, defina seu rótulo como 'attributeText', defina o número de linhas como 0 (que redimensionará o rótulo automaticamente (apenas no iOS 6 ou superior)) e defina-o como quebra de linha.

Depois, adiciono todas as alturas do conteúdo da célula na minha Classe de célula personalizada. No meu caso, eu tenho um Label na parte superior que sempre diz "Description" (_descriptionHeadingLabel), um rótulo menor de tamanho variável que contém a descrição real (_descriptionLabel), uma restrição da parte superior da célula ao cabeçalho (_descriptionHeadingLabelTopConstraint) . Também adicionei 3 ao espaço um pouco mais abaixo (aproximadamente a mesma quantidade que a maçã coloca na célula do tipo de legenda).

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

E no meu delegado 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;
}

Você pode alterar a instrução if para ser um pouco mais inteligente e obter o identificador da célula de algum tipo de fonte de dados. No meu caso, as células serão codificadas, uma vez que haverá uma quantidade fixa delas em uma ordem específica.


boundingRectWithSizeno ios 9.2 apresenta problemas, resultados diferentes são para ios <9.2. Você encontrou ou conhece outra maneira melhor de fazer isso.
Jose920405
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.