Modification dynamique de la taille de la police d'UILabel

188

J'ai actuellement un UILabel:

factLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 280, 100)];
factLabel.text = @"some text some text some text some text";
factLabel.backgroundColor = [UIColor clearColor];
factLabel.lineBreakMode = UILineBreakModeWordWrap;
factLabel.numberOfLines = 10;
[self.view addSubview:factLabel];

Tout au long de la vie de mon application iOS, factLabelobtient un tas de valeurs différentes. Certains avec plusieurs phrases, d'autres avec seulement 5 ou 6 mots.

Comment puis-je configurer le UILabelpour que la taille de la police change afin que le texte tienne toujours dans les limites que j'ai définies?

CodeGuy
la source
2
Pour 2016, je crois vraiment que la seule bonne solution est d'utiliser l'approche «utiliser l'auto-rétrécissement». Donnez à la boîte UILabel la taille réelle que vous voulez, faites en sorte que la police remplisse le UILabel, sélectionnez autoshrink, définissez une taille de police énorme (300) et assurez-vous de tester sur les simulateurs les plus petits / plus grands. (Donc, 4s / PadPro actuellement.) Explication complète: stackoverflow.com/a/35154493/294884 C'est la seule vraie solution aujourd'hui.
Fattie

Réponses:

370

Une seule ligne:

factLabel.numberOfLines = 1;
factLabel.minimumFontSize = 8;
factLabel.adjustsFontSizeToFitWidth = YES;

Le code ci-dessus ajustera la taille de la police de votre texte à (par exemple) 8 essayer d'adapter votre texte dans l'étiquette. numberOfLines = 1est obligatoire.

Plusieurs lignes:

Car numberOfLines > 1il existe une méthode pour déterminer la taille du texte final grâce aux méthodes d' addition sizeWithFont: ... UIKit de NSString , par exemple:

CGSize lLabelSize = [yourText sizeWithFont:factLabel.font
                                  forWidth:factLabel.frame.size.width
                             lineBreakMode:factLabel.lineBreakMode];

Après cela, vous pouvez simplement redimensionner votre étiquette en utilisant le résultat lLabelSize, par exemple (en supposant que vous ne changerez que la hauteur de l'étiquette):

factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, factLabel.frame.size.width, lLabelSize.height);

iOS6

Une seule ligne:

Depuis iOS6, minimumFontSizeest obsolète. La ligne

factLabel.minimumFontSize = 8.;

peut être changé en:

factLabel.minimumScaleFactor = 8./factLabel.font.pointSize;

IOS 7

Plusieurs lignes:

À partir d'iOS7, sizeWithFontdevient obsolète. Le cas multiligne est réduit à:

factLabel.numberOfLines = 0;
factLabel.lineBreakMode = NSLineBreakByWordWrapping;
CGSize maximumLabelSize = CGSizeMake(factLabel.frame.size.width, CGFLOAT_MAX);
CGSize expectSize = [factLabel sizeThatFits:maximumLabelSize];
factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, expectSize.width, expectSize.height);

iOS 13 (Swift 5):

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
Martin Babacaev
la source
mais cela met le texte sur une seule ligne. et si je change le factLabel.numberOfLines, alors la taille de la police ne change pas dynamiquement.
CodeGuy
@ reising1: vous avez raison. C'est juste comment faire fonctionner le cadre pour que le redimensionnement fonctionne pour vous.
Martin Babacaev le
alors la réponse à ma question est qu'il n'y a aucun moyen de le faire en utilisant le cadre fourni?
CodeGuy
1
@ reising1: Dans ce cas, vous pouvez également utiliser la méthode d'addition de NSString UIKit: sizeWithFont:constrainedToSize:lineBreakMode:Mais cette façon est un peu difficile
Martin Babacaev
6
Il est obsolète depuis iOS6. Remplacez-le parmyLabel.minimumScaleFactor:10.0/[UIFont labelFontSize];
Norbert
72

minimumFontSizeest obsolète avec iOS 6. Vous pouvez utiliser minimumScaleFactor.

yourLabel.adjustsFontSizeToFitWidth=YES;
yourLabel.minimumScaleFactor=0.5;

Cela prendra soin de la taille de votre police en fonction de la largeur de l'étiquette et du texte.

Amit Singh
la source
J'utilise généralement 0,8, car même 0,7 a tendance à paraître trop petit. Bien sûr, certains textes peuvent ne pas correspondre au facteur d'échelle minimum de 0,8, il s'agit de décider de ce qui semble le mieux et où les choses deviennent illisibles. OTOH mes applications peuvent être tournées, ce qui aide beaucoup.
gnasher729
adjustsFontSizeToFitWidthréduit uniquement le texte s'il ne rentre pas dans le conteneur
user25
24

Sur la base de la réponse de @Eyal Ben Dov, vous voudrez peut-être créer une catégorie pour la rendre flexible à utiliser dans d'autres applications de la vôtre.

Obs .: j'ai mis à jour son code pour le rendre compatible avec iOS 7

-En tête de fichier

#import <UIKit/UIKit.h>

@interface UILabel (DynamicFontSize)

-(void) adjustFontSizeToFillItsContents;

@end

-Fichier de mise en œuvre

#import "UILabel+DynamicFontSize.h"

@implementation UILabel (DynamicFontSize)

#define CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE 35
#define CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE 3

-(void) adjustFontSizeToFillItsContents
{
    NSString* text = self.text;

    for (int i = CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE; i>CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE; i--) {

        UIFont *font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
        NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];

        CGRect rectSize = [attributedText boundingRectWithSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];

        if (rectSize.size.height <= self.frame.size.height) {
            self.font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
            break;
        }
    }

}

@end

-Usage

#import "UILabel+DynamicFontSize.h"

[myUILabel adjustFontSizeToFillItsContents];

À votre santé

Paulo Miguel Almeida
la source
Ça ne fonctionne pas pour moi. Le contenu de mon UILabel est maintenant coupé.
Adrian
1
Si cela ne fonctionne pas pour vous, c'est probablement parce que le cadre de l'étiquette n'est pas encore défini. Essayez de définir le cadre avant d'appeler cela (ou appelez setNeedsLayout/ layoutIfNeededsi vous utilisez la mise en page automatique).
bmueller
Il donne le crash suivant "'NSInvalidArgumentException', raison: 'NSConcreteAttributedString initWithString :: nil value'"
Mohamed Saleh
Cela signifie que votre NSString ne peut pas être nul. Je suppose que si vous souhaitez ajuster la taille de la police pour remplir le contenu d'UILabel, vous devez au moins fournir un texte.
Paulo Miguel Almeida
Cela a un inconvénient. Il saute de ligne entre les caractères, de sorte que vous voyez les mots divisés en différentes lignes. Y a-t-il un moyen de contourner cela?
Özgür
24

Une seule ligne - Il y a deux façons, vous pouvez simplement changer.

1- Pragmatiquement (Swift 3)

Ajoutez simplement le code suivant

    yourLabel.numberOfLines = 1;
    yourLabel.minimumScaleFactor = 0.7;
    yourLabel.adjustsFontSizeToFitWidth = true;

2 - Utilisation de l'inspecteur d'attributs UILabel

i- Select your label- Set number of lines 1.
ii- Autoshrink-  Select Minimum Font Scale from drop down
iii- Set Minimum Font Scale value as you wish , I have set 0.7 as in below image. (default is 0.5)

entrez la description de l'image ici

Devendra Singh
la source
22

Nous sommes en 2015. Je devais aller chercher un article de blog qui expliquerait comment le faire pour la dernière version d'iOS et XCode avec Swift afin que cela fonctionne avec plusieurs lignes.

  1. définissez "Réduction automatique" sur "Taille de police minimale".
  2. définir la police sur la plus grande taille de police souhaitée (j'ai choisi 20)
  3. Remplacez "Sauts de ligne" de "Retour à la ligne" par "Tronquer la queue".

Source: http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

zavtra
la source
C'est vraiment une bouée de sauvetage !!
bhakti123 du
2
Super cool .. Ce point de queue tronqué est le plus important .. Coz en cas de mise en page automatique de retour à la ligne ne ressent pas le besoin de réduire la taille de la police, alors que quand elle est tronquée, la mise en page automatique de la queue doit enregistrer le texte de la lame et il c'est alors qu'il redimensionne la police.
GKK
12

Version Swift:

textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Esqarrouth
la source
Merci .. On dirait qu'ici, la séquence est aussi importante
Pramod
7

Voici une extension Swift pour UILabel. Il exécute un algorithme de recherche binaire pour redimensionner la police en fonction de la largeur et de la hauteur des limites de l'étiquette. Testé pour fonctionner avec iOS 9 et la mise en page automatique.

UTILISATION: Où se <label>trouve votre UILabel prédéfini qui doit être redimensionné

<label>.fitFontForSize()

Par défaut, cette fonction recherche dans la plage des tailles de police 5 et 300 pt et définit la police pour qu'elle s'adapte «parfaitement» à son texte dans les limites (exactes à 1,0 pt). Vous pouvez définir les paramètres pour qu'il recherche, par exemple, entre 1 pt et la taille de police actuelle de l' étiquette avec précision dans les 0,1 pt de la manière suivante:

<label>.fitFontForSize(1.0, maxFontSize: <label>.font.pointSize, accuracy:0.1)

Copiez / collez le code suivant dans votre fichier

extension UILabel {

    func fitFontForSize(var minFontSize : CGFloat = 5.0, var maxFontSize : CGFloat = 300.0, accuracy : CGFloat = 1.0) {
        assert(maxFontSize > minFontSize)
        layoutIfNeeded() // Can be removed at your own discretion
        let constrainedSize = bounds.size
        while maxFontSize - minFontSize > accuracy {
            let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
            font = font.fontWithSize(midFontSize)
            sizeToFit()
            let checkSize : CGSize = bounds.size
            if  checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
                minFontSize = midFontSize
            } else {
                maxFontSize = midFontSize
            }
        }
        font = font.fontWithSize(minFontSize)
        sizeToFit()
        layoutIfNeeded() // Can be removed at your own discretion
    }

}

REMARQUE: Chacun des layoutIfNeeded()appels peut être supprimé à votre propre discrétion

Avi Frankl
la source
Ah - mais cela ne fonctionne pas vraiment avec la mise en page automatique; les "sizeToFit" ne font rien dans ce cas.
Fattie
4

Ce n'est pas un peu sophistiqué mais cela devrait fonctionner, par exemple, disons que vous voulez limiter votre uilabel à 120x120, avec une taille de police maximale de 28:

magicLabel.numberOfLines = 0;
magicLabel.lineBreakMode = NSLineBreakByWordWrapping;
...
magicLabel.text = text;
    for (int i = 28; i>3; i--) {
        CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:(CGFloat)i] constrainedToSize:CGSizeMake(120.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        if (size.height < 120) {
            magicLabel.font = [UIFont systemFontOfSize:(CGFloat)i];
            break;
        }
    }
Eyal Ben Dov
la source
Cela semble plutôt inefficace - vous devriez laisser l'UILabel se hisser dynamiquement pour s'adapter à l'espace disponible fourni. Si vous exécutez cela pour quelque chose comme le calcul de la police de titre d'une cellule de vue tableau, vous obtiendrez des problèmes de retard majeurs. L'approche peut fonctionner, mais certainement pas recommandée.
Zorayr
Votez pour être la seule personne à répondre à la question.
Jai
2

Envoyez simplement le message sizeToFit à UITextView. Il ajustera sa propre hauteur pour s'adapter à son texte. Il ne changera pas sa propre largeur ou origine.

[textViewA1 sizeToFit];
codercat
la source
Que se passe-t-il lorsque la taille qui correspond au texte est trop grande pour l'espace du conteneur? Par exemple, disons que vous avez 100 points disponibles pour s'adapter à la vue texte, après avoir appelé sizeToFitvotre textViewA1devient 200 points qui finissent par être rognés.
Zorayr
0

Version Swift 2.0:

private func adapteSizeLabel(label: UILabel, sizeMax: CGFloat) {
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.ByWordWrapping
     let maximumLabelSize = CGSizeMake(label.frame.size.width, sizeMax);
     let expectSize = label.sizeThatFits(maximumLabelSize)
     label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, expectSize.width, expectSize.height)
}
Phil
la source
0

Cette solution fonctionne pour la multiligne:

Après avoir suivi plusieurs articles et avoir exigé une fonction qui redimensionnerait automatiquement le texte et ajusterait le nombre de lignes pour mieux s'adapter à la taille d'étiquette donnée, j'ai écrit une fonction moi-même. (c'est-à-dire qu'une chaîne courte tiendrait bien sur une ligne et utiliserait une grande partie du cadre de l'étiquette, tandis qu'une longue chaîne se diviserait automatiquement en 2 ou 3 lignes et ajusterait la taille en conséquence)

N'hésitez pas à le réutiliser et à le modifier si nécessaire. Assurez-vous de l'appeler après viewDidLayoutSubviewsavoir terminé afin que le cadre d'étiquette initial ait été défini.

+ (void)setFontForLabel:(UILabel *)label withMaximumFontSize:(float)maxFontSize andMaximumLines:(int)maxLines {
    int numLines = 1;
    float fontSize = maxFontSize;
    CGSize textSize; // The size of the text
    CGSize frameSize; // The size of the frame of the label
    CGSize unrestrictedFrameSize; // The size the text would be if it were not restricted by the label height
    CGRect originalLabelFrame = label.frame;

    frameSize = label.frame.size;
    textSize = [label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize: fontSize]}];

    // Work out the number of lines that will need to fit the text in snug
    while (((textSize.width / numLines) / (textSize.height * numLines) > frameSize.width / frameSize.height) && (numLines < maxLines)) {
        numLines++;
    }

    label.numberOfLines = numLines;

    // Get the current text size
    label.font = [UIFont systemFontOfSize:fontSize];
    textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                        options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                     attributes:@{NSFontAttributeName : label.font}
                                        context:nil].size;

    // Adjust the frame size so that it can fit text on more lines
    // so that we do not end up with truncated text
    label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, label.frame.size.width);

    // Get the size of the text as it would fit into the extended label size
    unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;

    // Keep reducing the font size until it fits
    while (textSize.width > unrestrictedFrameSize.width || textSize.height > frameSize.height) {
        fontSize--;
        label.font = [UIFont systemFontOfSize:fontSize];
        textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                            options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                         attributes:@{NSFontAttributeName : label.font}
                                            context:nil].size;
        unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
    }

    // Set the label frame size back to original
    label.frame = originalLabelFrame;
}
Aaroncatlin
la source
0

Voici le code de remplissage d'une sous-classe UILabel qui implémente le changement de taille de police animé:

@interface SNTextLayer : CATextLayer

@end

@implementation SNTextLayer

- (void)drawInContext:(CGContextRef)ctx {
    // We override this to make text appear at the same vertical positon as in UILabel
    // (otherwise it's shifted tdown)
    CGFloat height = self.bounds.size.height;
    float fontSize = self.fontSize;
    // May need to adjust this somewhat if it's not aligned perfectly in your implementation
    float yDiff = (height-fontSize)/2 - fontSize/10;

    CGContextSaveGState(ctx);
    CGContextTranslateCTM(ctx, 0.0, yDiff);
    [super drawInContext:ctx];
     CGContextRestoreGState(ctx);
}

@end

@interface SNAnimatableLabel ()

@property CATextLayer* textLayer;

@end

@interface SNAnimatableLabel : UILabel

- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration;

@end



@implementation SNAnimatableLabel


- (void)awakeFromNib {
    [super awakeFromNib];
    _textLayer = [SNTextLayer new];
    _textLayer.backgroundColor = self.backgroundColor.CGColor;
    _textLayer.foregroundColor = self.textColor.CGColor;
    _textLayer.font = CGFontCreateWithFontName((CFStringRef)self.font.fontName);
    _textLayer.frame = self.bounds;
    _textLayer.string = self.text;
    _textLayer.fontSize = self.font.pointSize;
    _textLayer.contentsScale = [UIScreen mainScreen].scale;
    [_textLayer setPosition: CGPointMake(CGRectGetMidX(_textLayer.frame), CGRectGetMidY(_textLayer.frame))];
    [_textLayer setAnchorPoint: CGPointMake(0.5, 0.5)];
    [_textLayer setAlignmentMode: kCAAlignmentCenter];
    self.textColor = self.backgroundColor;
    // Blend text with background, so that it doens't interfere with textlayer text
    [self.layer addSublayer:_textLayer];
    self.layer.masksToBounds = NO;
}

- (void)setText:(NSString *)text {
    _textLayer.string = text;
    super.text = text;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    // Need to enlarge the frame, otherwise the text may get clipped for bigger font sizes
    _textLayer.frame = CGRectInset(self.bounds, -5, -5);
}

- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration {
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];
    _textLayer.fontSize = fontSize;
    [CATransaction commit];
}
vidéoliste
la source