Remplacement de la taille obsolèteWithFont: dans iOS 7?

320

Dans iOS 7, sizeWithFont:est désormais obsolète. Comment puis-je maintenant passer l'objet UIFont dans la méthode de remplacement sizeWithAttributes:?

James Kuang
la source

Réponses:

521

Utilisez sizeWithAttributes:plutôt, qui prend maintenant un NSDictionary. Passez la paire avec la clé UITextAttributeFontet votre objet de police comme ceci:

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));
James Kuang
la source
60
lorsque vous travaillez avec un NSStringet un UILabel(pas TOUJOURS le cas, mais souvent), pour éviter la duplication de code / etc., vous pouvez également remplacer [UIFont systemFontOfSize:17.0f]par label.font- aide à la maintenance du code en référençant les données existantes par rapport à vous en les tapant plusieurs fois ou en référençant des constantes partout dans le place, etc
toblerpwn
8
@toblerpwn il est possible que l'étiquette n'existe pas et que vous essayez de calculer une étiquette théorique.
botbot
5
Comment pourrais-je utiliser cet exemple pour obtenir la taille.hauteur avec une étiquette qui a une largeur fixe de 250 par exemple? OU si c'est une étiquette avec autolatyout qui prend un pourcentage de la largeur et je passe en mode paysager.
Pedroinpeace
12
@Pedroinpeace Vous utiliseriez à la boundingRectWithSize:options:attributes:context:place, en passant CGSizeMake(250.0f, CGFLOAT_MAX)dans la plupart des cas.
James Kuang
9
Autre chose à noter sizeWithAttributes: n'est pas 100% équivalent. sizeWithFont utilisé pour arrondir les tailles aux valeurs entières (pixels). Suggérez d'utiliser ceilf sur la hauteur / largeur résultante ou vous pouvez obtenir des artefacts flous (en particulier non rétiniens HW) si vous l'utilisez pour les calculs de position.
Nick H247
172

Je crois que la fonction était obsolète parce que cette série de NSString+UIKitfonctions ( sizewithFont:..., etc.) était basée sur la UIStringDrawingbibliothèque, qui n'était pas sûre pour les threads. Si vous avez essayé de les exécuter non pas sur le thread principal (comme toute autre UIKitfonctionnalité), vous obtiendrez des comportements imprévisibles. En particulier, si vous exécutez la fonction sur plusieurs threads simultanément, cela bloquera probablement votre application. C'est pourquoi dans iOS 6, ils ont introduit une boundingRectWithSize:...méthode pour NSAttributedString. Cela a été construit au-dessus des NSStringDrawingbibliothèques et est thread-safe.

Si vous regardez la nouvelle NSString boundingRectWithSize:...fonction, elle demande un tableau d'attributs de la même manière qu'un a NSAttributeString. Si je devais deviner, cette nouvelle NSStringfonction dans iOS 7 est simplement un wrapper pour la NSAttributeStringfonction d'iOS 6.

Sur cette note, si vous ne preniez en charge que iOS 6 et iOS 7, je changerais certainement tous vos fichiers NSString sizeWithFont:...en NSAttributeString boundingRectWithSize. Cela vous évitera beaucoup de maux de tête si vous avez un étui d'angle multi-threading étrange! Voici comment j'ai converti NSString sizeWithFont:constrainedToSize::

Ce qui était autrefois:

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

Peut être remplacé par:

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;

Veuillez noter que la documentation mentionne:

Dans iOS 7 et versions ultérieures, cette méthode renvoie des tailles fractionnaires (dans le composant de taille du retour CGRect); pour utiliser une taille retournée à des vues de taille, vous devez utiliser augmenter sa valeur à l'entier supérieur le plus proche à l'aide de la fonction ceil.

Donc, pour extraire la hauteur ou la largeur calculée à utiliser pour dimensionner les vues, j'utiliserais:

CGFloat height = ceilf(size.height);
CGFloat width  = ceilf(size.width);
Mr. T
la source
boundingRectWithSize est déconseillé dans iOS 6.0.
Nirav
2
@Nirav Je ne vois aucune mention de la dépréciation. Pouvez-vous m'indiquer où il est dit qu'il est obsolète? Merci! ( Developer.apple.com/library/ios/documentation/uikit/reference/... )
M. T
2
Peut-être que @Nirav signifiait qu'il n'était pas disponible pour NSString dans iOS 6 (ce qui est mentionné indirectement dans la réponse)?
newenglander
1
@VagueExplanation Je viens d'essayer cela et boundingRectForSize n'est toujours pas déconseillé pour NSAttributedString. Il ne dit pas non plus obsolète dans la documentation.
M. T
5
Fantastique pour mentionner l'utilisation de ceilf (). Nulle part ailleurs cela n'a été mentionné et ma taille était toujours légèrement trop petite. Merci!
Jonathan Brown
29

Comme vous pouvez le voir sizeWithFontsur le site Apple Developer, il est obsolète, nous devons donc l'utiliser 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
la source
17
Dans ce cas, il vaut mieux utiliser la [NSObject respondsToSelector:]méthode comme ici: stackoverflow.com/a/3863039/1226304
derpoliuk
16

J'ai créé une catégorie pour gérer ce problème, la voici:

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

De cette façon, vous n'avez qu'à trouver / remplacer sizeWithFont:par sizeWithMyFont:et vous êtes prêt à partir.

ROM.
la source
1
bien que cela produise un avertissement du compilateur sur ios7 + pour référencer sizeWithFont, ou un avertissement / erreur de compilation sur <ios7 pour référencer sizeWithAttributes! Il est probablement préférable d'utiliser une macro au lieu de vérifier respondsToSelector - si nécessaire. Mais comme vous ne pouvez plus livrer à Apple avec le SDK ioS6 ... ce n'est probablement pas le cas!
Nick H247
Ajoutez ceci pour supprimer l'avertissement #pragma GCC diagnostic ignoré "-Wdeprecated-declarations"
Ryan Heitner
10

Dans iOS7, j'avais besoin de la logique pour renvoyer la hauteur correcte pour la méthode tableview: heightForRowAtIndexPath, mais la tailleWithAttributes renvoie toujours la même hauteur quelle que soit la longueur de la chaîne car elle ne sait pas qu'elle va être placée dans une cellule de tableau à largeur fixe . J'ai trouvé que cela fonctionne très bien pour moi et calcule la bonne hauteur en tenant compte de la largeur de la cellule du tableau! Ceci est basé sur la réponse de M. T ci-dessus.

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
la source
7

Les étiquettes multilignes utilisant la hauteur dynamique peuvent nécessiter des informations supplémentaires pour définir correctement la taille. Vous pouvez utiliser sizeWithAttributes avec UIFont et NSParagraphStyle pour spécifier à la fois la police et le mode de saut de ligne.

Vous définiriez le style de paragraphe et utiliseriez un NSDictionary comme ceci:

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

Vous pouvez utiliser la propriété CGSize 'adjustSize' ou CGRect comme propriété rect.size.height si vous recherchez la hauteur.

Plus d'informations sur NSParagraphStyle ici: https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSParagraphStyle_Class/Reference/Reference.html

bitsand
la source
1
Il semble qu'il y ait un problème de syntaxe immédiatement après adjustSize.
Chris Prince
Merci d'avoir rattrapé ce problème de syntaxe, Chris
bitsand
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));
Kirit Vaghela
la source
1
Cela m'a sauvé des heures de maux de tête, merci Kirit, définir les attributs et les dictionnaires avant le bloc CGRect était ce qui m'a fait ... aussi beaucoup plus facile à lire.
Azin Mehrnoosh
3

Créez une fonction qui prend une instance UILabel. et retourne 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;
Muhammad Idris
la source
Dans mon cas pour la hauteur de ligne de manière dynamique, j'ai complètement supprimé la méthode HeightForRow et utilisé UITableViewAutomaticDimension. tableView.estimatedRowHeight = 68.0 tableView.rowHeight = UITableViewAutomaticDimension
Eugene Braginets
3

Solution alternative-

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];
}
DareDevil
la source
1
Cela produira un avertissement du compilateur sur iOS6 et 7. Et aura un comportement assez différent pour chacun!
Nick H247
3

S'appuyant sur @bitsand, il s'agit d'une nouvelle méthode que je viens d'ajouter à ma catégorie 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;
}

J'utilise juste la taille de l'image résultante.

Chris Prince
la source
2

Vous pouvez toujours utiliser sizeWithFont. mais, dans la méthode iOS> = 7.0, le blocage se produit si la chaîne contient des espaces ou des lignes de fin et de début \n.

Découper du texte avant de l'utiliser

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

Cela peut également s'appliquer à sizeWithAttributeset [label sizeToFit].

aussi, chaque fois que vous avez un nsstringdrawingtextstorage message sent to deallocated instanceappareil iOS 7.0, il traite cela.

hasan
la source
2

Mieux utiliser les dimensions automatiques (Swift):

  tableView.estimatedRowHeight = 68.0
  tableView.rowHeight = UITableViewAutomaticDimension

NB: 1. Le prototype UITableViewCell doit être correctement conçu (pour l'instance, n'oubliez pas de définir UILabel.numberOfLines = 0, etc.) 2. Supprimer la méthode HeightForRowAtIndexPath

entrez la description de l'image ici

VIDÉO: https://youtu.be/Sz3XfCsSb6k

Eugene Braginets
la source
Il faut une hauteur de cellule statique définie dans la cellule prototype du storyboard
Kirit Vaghela
J'ai ajouté ces deux lignes et je me suis assuré que mon UILabel était réglé sur 0 nombre de lignes. N'a pas fonctionné comme annoncé. Qu'avez-vous fait d'autre pour le faire fonctionner avec seulement ces deux lignes de code?
Will
1
hm, vous devez également épingler vos contraintes d'étiquette en haut et en bas de la cellule
Eugene Braginets
1
Non, cela devrait suffire. Avez-vous des contraintes de mise en page automatique pour le libellé épinglé à la vue d'ensemble?
Eugene Braginets
1
boundingRectWithSize:options:attributes:context:
Holden
la source
1

La réponse acceptée dans Xamarin serait (utilisez sizeWithAttributes et UITextAttributeFont):

        UIStringAttributes attributes = new UIStringAttributes
        { 
            Font = UIFont.SystemFontOfSize(17) 
        }; 
        var size = text.GetSizeUsingAttributes(attributes);
Alex Sorokoletov
la source
1

Comme la réponse @Ayush:

Comme vous pouvez le voir sizeWithFontsur le site Apple Developer, il est obsolète, nous devons donc l'utiliser sizeWithAttributes.

Eh bien, en supposant qu'en 2019+, vous utilisez probablement Swift et Stringau lieu d'Objective-c et NSString, voici la bonne façon d'obtenir la taille d'un Stringavec une police prédéfinie:

let stringSize = NSString(string: label.text!).size(withAttributes: [.font : UIFont(name: "OpenSans-Regular", size: 15)!])
Romulo BM
la source
Comment pourriez-vous l'utiliser pour déterminer la taille de la police?
Joseph Astrahan
@JosephAstrahan ce n'est pas pour déterminer la taille de la police, c'est pour obtenir la taille (CGSize) d'une chaîne avec une police déterminée. Si vous voulez obtenir la taille de police d'une étiquette, vous pouvez facilement utiliser label.font
Romulo BM
en utilisant votre idée, j'ai créé un moyen d'obtenir la taille de la police plus ou moins ( stackoverflow.com/questions/61651614/… ), label.font ne fonctionnerait pas dans mon cas en raison de problèmes complexes avec les étiquettes pouvant être cliquées uniquement si le la police exacte est connue, vous pouvez lire à ce sujet sur mon 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]);
}
Prosenjit Goswami
la source
0

Voici l'équivalent monotouch si quelqu'un en a besoin:

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

qui peut être utilisé comme ceci:

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);
}
Roosevelt
la source
0
CGSize maximumLabelSize = CGSizeMake(label.frame.size.width, FLT_MAX);
CGSize expectedLabelSize = [label sizeThatFits:maximumLabelSize];
float heightUse = expectedLabelSize.height;
user1802778
la source
0

Essayez cette syntaxe:

NSAttributedString *attributedText =
    [[NSAttributedString alloc] initWithString:text 
                                    attributes:@{NSFontAttributeName: font}];
Muruganandam Sathasivam
la source
-1

Rien de tout cela n'a fonctionné pour moi dans iOS 7. Voici ce que j'ai fini par faire. Je mets cela dans ma classe de cellule personnalisée et appelle la méthode dans ma méthode heightForCellAtIndexPath.

Ma cellule ressemble à la cellule de description lors de l'affichage d'une application dans l'App Store.

Tout d'abord dans le storyboard, définissez votre étiquette sur 'AttribedText', définissez le nombre de lignes sur 0 (ce qui redimensionnera automatiquement l'étiquette (iOS 6+ uniquement)) et définissez-le sur un retour à la ligne.

Ensuite, je viens d'ajouter toutes les hauteurs du contenu de la cellule dans ma classe de cellule personnalisée. Dans mon cas, j'ai une étiquette en haut qui dit toujours "Description" (_descriptionHeadingLabel), une étiquette plus petite de taille variable qui contient la description réelle (_descriptionLabel) une contrainte du haut de la cellule à l'en-tête (_descriptionHeadingLabelTopConstraint) . J'ai également ajouté 3 pour espacer un peu le bas (environ la même quantité de pomme place sur la cellule de type de sous-titre).

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

Et dans mon délégué 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;
}

Vous pouvez modifier l'instruction if pour qu'elle soit un peu plus «intelligente» et obtenir réellement l'identifiant de cellule à partir d'une sorte de source de données. Dans mon cas, les cellules vont être codées en dur car il y en aura un nombre fixe dans un ordre spécifique.

Kubee
la source
boundingRectWithSizedans ios 9.2 présente des problèmes, les résultats sont différents de ios <9.2. Vous avez trouvé ou connaissez une autre meilleure façon de procéder.
jose920405