web-dev-qa-db-fra.com

Autoshrink sur un UILabel à plusieurs lignes

Est-il possible d'utiliser la propriété autoshrink conjointement sur plusieurs lignes sur une UILabel? par exemple, la taille de texte importante possible sur 2 lignes disponibles.

52
jfisk

Ces personnes ont trouvé une solution:

http://www.11pixel.com/blog/28/resize-multi-line-text-to-fit-uilabel-on-iphone/

Leur solution est la suivante:

int maxDesiredFontSize = 28;
int minFontSize = 10;
CGFloat labelWidth = 260.0f;
CGFloat labelRequiredHeight = 180.0f;
//Create a string with the text we want to display.
self.ourText = @"This is your variable-length string. Assign it any way you want!";

/* This is where we define the ideal font that the Label wants to use.
   Use the font you want to use and the largest font size you want to use. */
UIFont *font = [UIFont fontWithName:@"Marker Felt" size:maxDesiredFontSize];

int i;
/* Time to calculate the needed font size.
   This for loop starts at the largest font size, and decreases by two point sizes (i=i-2)
   Until it either hits a size that will fit or hits the minimum size we want to allow (i > 10) */
for(i = maxDesiredFontSize; i > minFontSize; i=i-2)
{
    // Set the new font size.
    font = [font fontWithSize:i];
    // You can log the size you're trying: NSLog(@"Trying size: %u", i);

    /* This step is important: We make a constraint box 
       using only the fixed WIDTH of the UILabel. The height will
       be checked later. */ 
    CGSize constraintSize = CGSizeMake(labelWidth, MAXFLOAT);

    // This step checks how tall the label would be with the desired font.
    CGSize labelSize = [self.ourText sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];

    /* Here is where you use the height requirement!
       Set the value in the if statement to the height of your UILabel
       If the label fits into your required height, it will break the loop
       and use that font size. */
    if(labelSize.height <= labelRequiredHeight)
        break;
}
// You can see what size the function is using by outputting: NSLog(@"Best size is: %u", i);

// Set the UILabel's font to the newly adjusted font.
msg.font = font;

// Put the text into the UILabel outlet variable.
msg.text = self.ourText;

Pour que cela fonctionne, un IBOutlet doit être affecté dans le générateur d'interface à UILabel.

"IBOutlet UILabel * msg;"

Tout le mérite est du peuple à 11pixel.

39
The dude

J'ai légèrement modifié le code ci-dessus pour en faire une catégorie sur UILabel:

En tête de fichier:

#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
    - (void)adjustFontSizeToFit;
@end

Et le fichier d'implémentation:

@implementation UILabel (MultiLineAutoSize)

- (void)adjustFontSizeToFit
{
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumFontSize; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}

@end
46
DaGaMs

J'ai trouvé ce lien http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

Le problème peut être résolu à l'aide d'Interface Builder en 3 étapes simples:

  1. Définissez «Autoshrink» sur «Taille minimale de la police». 
  2. Définissez la police sur votre plus grande taille de police souhaitable (20) et définissez Lines sur 10, par exemple, ce qui dans mon cas était aussi nombreux que le nombre de lignes pouvant être insérées dans l'étiquette à cette taille de police. 
  3. Ensuite, remplacez «Retour à la ligne» par «Ligne Breaks».

J'espère que ça aide!

37
MontiRabbit

Voici la solution de catégorie mise à jour pour iOS 7 basée sur les mises à jour d'itecedor pour iOS 6.

En tête de fichier:

#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
    - (void)adjustFontSizeToFit;
@end

Et le fichier d'implémentation:

@implementation UILabel (MultiLineAutoSize)


- (void)adjustFontSizeToFit {
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);

        CGRect textRect = [self.text boundingRectWithSize:constraintSize
                                             options:NSStringDrawingUsesLineFragmentOrigin
                                          attributes:@{NSFontAttributeName:font}
                                             context:nil];

        CGSize labelSize = textRect.size;


        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout]; }


@end
24
stevenpaulr

La réponse indiquée comme solution est hacky et imprécise. UILabel le gérera automatiquement si vous définissez correctement les propriétés suivantes:

numberOfLines doit être différent de zéro

adjustsFontSizeToFitWidth doit être YES

lineBreakMode doit pas être NSLineBreakByCharWrapping ou NSLineBreakByWordWrapping

10
GoldenJoe

Une version rapide adaptée de @DaGaMs. 

Swift 2:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.fontWithSize(size)
            let constraintSize = CGSizeMake(bounds.size.width, CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRectWithSize(constraintSize,
                options: .UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName: proposedFont],
                context: nil)
            if labelSize.height <= bounds.size.height {
                font = proposedFont
                setNeedsLayout()
                break;
            }
        }
    }
}

Swift 3:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.withSize(size)
            let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
                                                                            options: .usesLineFragmentOrigin,
                                                                            attributes: [NSFontAttributeName: proposedFont],
                                                                            context: nil)
            if labelSize.height <= bounds.size.height {
                font = proposedFont
                setNeedsLayout()
                break;
            }
        }
    }
}
7
wfbarksdale

Je ne peux pas commenter le post de MontiRabbit en raison d'un manque de réputation, je vais donc vous donner une nouvelle réponse. La solution proposée par lui (et son référent) ne fonctionne pas sur Xcode 7.3 ou mieux, c'est imprécis . Pour que cela fonctionne, dans le storyboard, je devais:

  1. Définir une contrainte de largeur (largeur pure ou tail & lead)
  2. RÉGLER UNE CONTRAINTE DE HAUTEUR (ceci est très important, normalement, avec une taille automatique, on ne règle pas la hauteur de l'étiquette)
  3. Définissez la propriété "Autoshrink" sur "Échelle de police minimale" ou "Taille de police minimale" (fonctionne dans les deux cas).
  4. Définissez la propriété "Line Breaks" sur "Truncate Tail"
  5. Définissez la propriété "Lignes" sur une valeur non nulle

J'espère que ça aide! ;)

7
Nem

j'ai aimé la réponse de DaGaMs, mais en utilisant des étiquettes comme dans UITableViewCells qui pourraient être renvoyées de dequeueReusableCell :, la taille de police normale continuerait à diminuer même si la taille de police d'origine était toujours souhaitée pour certaines cellules de tableView contenant moins de texte et pouvant tirer parti de la taille de police d'origine de l'étiquette d'origine.

donc, en commençant par la catégorie de DaGaMs comme point de départ, j'ai créé une classe séparée plutôt qu'une catégorie séparée, et je m'assure que mes UILabels dans mon storyboard utilisent cette nouvelle classe:

#import "MultiLineAutoShrinkLabel.h"

@interface MultiLineAutoShrinkLabel ()
@property (readonly, nonatomic) UIFont* originalFont;
@end

@implementation MultiLineAutoShrinkLabel

@synthesize originalFont = _originalFont;

- (UIFont*)originalFont { return _originalFont ? _originalFont : (_originalFont = self.font); }

- (void)quoteAutoshrinkUnquote
{
    UIFont* font = self.originalFont;
    CGSize frameSize = self.frame.size;

    CGFloat testFontSize = _originalFont.pointSize;
    for (; testFontSize >= self.minimumFontSize; testFontSize -= 0.5)
    {
        CGSize constraintSize = CGSizeMake(frameSize.width, MAXFLOAT);
        CGSize testFrameSize = [self.text sizeWithFont:(font = [font fontWithSize:testFontSize])
                                     constrainedToSize:constraintSize
                                         lineBreakMode:self.lineBreakMode];
        // the ratio of testFontSize to original font-size sort of accounts for number of lines
        if (testFrameSize.height <= frameSize.height * (testFontSize/_originalFont.pointSize))
            break;
    }

    self.font = font;
    [self setNeedsLayout];
}

@end
6
john.k.doe

la réponse de itedcedor a un problème que pwightman a souligné. En outre, il n'est pas nécessaire de réduire les espaces blancs. Ici c'est la version modifiée:

- (void)adjustFontSizeToFit {
    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f) {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];

        if(labelSize.height <= size.height) {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }

    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}
6
Abras

Merci à DaGaMs pour cette solution.

Je l'ai mis à jour comme suit:

1 - Travailler avec iOS 6 (étant donné que minimumFontSize et UILineBreakModeWordWrap sont obsolètes) 2 - Supprimer les espaces du texte de l'étiquette, car le redimensionnement échouera (vous ne voulez pas savoir combien de temps cela a pris trouver ce bug)

-(void)adjustFontSizeToFit 
{
    self.text = [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

    UIFont *font = self.font;
    CGSize size = self.frame.size;

    for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor; maxSize -= 1.f)
    {
        font = [font fontWithSize:maxSize];
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
        if(labelSize.height <= size.height)
        {
            self.font = font;
            [self setNeedsLayout];
            break;
        }
    }
    // set the font to the minimum size anyway
    self.font = font;
    [self setNeedsLayout];
}
4
itecedor

Pour UIButton, seules ces lignes fonctionnent pour moi:

self.centerBtn.titleLabel.numberOfLines = 2;
self.centerBtn.titleLabel.textAlignment = NSTextAlignmentCenter;
self.centerBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
2
Geman Wu

J'ai utilisé la solution Swift 3 de @ wbarksdale, mais j'ai constaté que les mots longs étaient tronqués au milieu. Pour garder les mots intacts, j'ai dû modifier comme indiqué:

extension UILabel {
    func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
        let maxFontSize = maximumFontSize ?? font.pointSize
        let words = self.text?.components(separatedBy: " ")
        var longestWord: String?
        if let max = words?.max(by: {$1.characters.count > $0.characters.count}) {
            longestWord = max
        }
        for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
            let proposedFont = font.withSize(size)
            let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
            let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
                                                                    options: .usesLineFragmentOrigin,
                                                                    attributes: [NSFontAttributeName: proposedFont],
                                                                    context: nil)

            let wordConstraintSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))
            let longestWordSize = ((longestWord ?? "") as NSString).boundingRect(with: wordConstraintSize,
                                                                    options: .usesFontLeading,
                                                                    attributes: [NSFontAttributeName: proposedFont],
                                                                    context: nil)

            if labelSize.height <= bounds.size.height && longestWordSize.width < constraintSize.width {
                font = proposedFont
                setNeedsLayout()
                break
            }
        }
    }
}
2
Daniel McLean

J'ai écrit une petite catégorie sur UILabel basée sur la réponse ci-dessus "The Dude's" pour obtenir cette fonctionnalité.

https://Gist.github.com/ayushn21/d87b835b2efc756e859f

1
ayushn21

Essaye ça:

Sous-classe UILabel ou appelez la méthode adjustFontSize après avoir défini la propriété text sur une étiquette

override var text : String? { didSet { self.adjustFontSize() } }

func adjustFontSize()
{
    var lineCount = self.string.components(separatedBy: "\n").count - 1
    var textArray = self.string.components(separatedBy: " ")
    var wordsToCompare = 1
    while(textArray.count > 0)
    {
        let words = textArray.first(n: wordsToCompare).joined(separator: " ")
        let wordsWidth = words.widthForHeight(0, font: self.font)
        if(wordsWidth > self.frame.width)
        {
            textArray.removeFirst(wordsToCompare)
            lineCount += 1
            wordsToCompare = 1
        }
        else if(wordsToCompare > textArray.count)
        {
            break
        }
        else
        {
            wordsToCompare += 1
        }
    }
    self.numberOfLines = lineCount + 1
}
0
Rendel
extension UILabel{

func adjustFont(minSize:Int, maxSize:Int){
    var newFont = self.font
    for index in stride(from: maxSize, to: minSize, by: -1) {
        newFont = UIFont.systemFont(ofSize: CGFloat(index))
        let size = CGSize(width: self.frame.width, height: CGFloat(Int.max))
        let size2 = (self.text! as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font:newFont!], context: nil).size
        if size2.height < self.frame.size.height{
            break
        }
    }
    self.font = newFont
}

}

vous devez également affecter une valeur à la propriété numberOfLines de UILabel.

0
cnu

Il existe une méthode sur NSString, -sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: qui existe apparemment depuis iOS 2.0, mais est malheureusement déconseillée dans iOS 7 sans autre solution suggérée, car la réduction automatique de la taille de la police est déconseillée. Je ne comprends pas vraiment la position d'Apple à ce sujet, car ils l'utilisent dans Keynote, etc., et je pense que si les tailles de police sont comprises dans une petite plage, c'est correct. Voici une implémentation dans Swift utilisant cette méthode.

var newFontSize: CGFloat = 30
    let font = UIFont.systemFontOfSize(newFontSize)
    (self.label.text as NSString).sizeWithFont(font, minFontSize: 20, actualFontSize: &newFontSize, forWidth: self.label.frame.size.width, lineBreakMode: NSLineBreakMode.ByWordWrapping)
    self.label.font = font.fontWithSize(newFontSize)

Je ne suis pas au courant d'un moyen d'y parvenir sans utiliser des méthodes obsolètes.

0
Nick