Dynamicznie zmieniający się rozmiar czcionki UILabel

188

Obecnie mam 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];

Przez całe życie mojej aplikacji na iOS factLabeldostaje mnóstwo różnych wartości. Niektóre z wieloma zdaniami, inne z zaledwie 5 lub 6 słowami.

Jak mogę ustawić UILabeltak, aby rozmiar czcionki zmieniał się, aby tekst zawsze mieścił się w zdefiniowanych przeze mnie granicach?

CodeGuy
źródło
2
Naprawdę uważam, że w 2016 r. Jedynym dobrym rozwiązaniem jest zastosowanie metody „użyj automatycznego ograniczania prędkości”. Ustaw pole UILabel w żądanym rozmiarze, spraw, aby czcionka wypełniała UILabel, wybierz autoshrink, ustaw tytułowy ogromny rozmiar czcionki (300) i koniecznie przetestuj na najmniejszych / największych symulatorach. (Tak więc obecnie 4s / PadPro.) Pełne wyjaśnienie: stackoverflow.com/a/35154493/294884 To jedyne prawdziwe rozwiązanie na dziś.
Fattie

Odpowiedzi:

370

Pojedyncza linia:

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

Powyższy kod dostosuje rozmiar czcionki tekstu do (na przykład) 8próby dopasowania tekstu do etykiety. numberOfLines = 1jest obowiązkowe.

Wiele linii:

Dla numberOfLines > 1istnieje metoda, aby dowiedzieć się rozmiar tekstu końcowego przez NSString za sizeWithFont ... addycyjnych UIKit metody, na przykład:

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

Następnie możesz po prostu zmienić rozmiar etykiety przy użyciu wynikowego lLabelSize, na przykład (zakładając, że zmienisz tylko wysokość etykiety):

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

iOS6

Pojedyncza linia:

Począwszy od iOS6, minimumFontSizebył przestarzały. Linia

factLabel.minimumFontSize = 8.;

można zmienić na:

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

iOS7

Wiele linii:

Począwszy od iOS7, sizeWithFontstaje się przestarzały. Obudowa multilinii została zredukowana do:

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
źródło
ale to umieszcza tekst w jednym wierszu. a jeśli zmienię factLabel.numberOfLines, wówczas rozmiar czcionki nie zmienia się dynamicznie.
CodeGuy
@ reising1: masz rację. Oto jak stworzyć ramy do zmiany rozmiaru pracy.
Martin Babacaev,
więc odpowiedź na moje pytanie brzmi: nie ma sposobu, aby to zrobić przy użyciu dostarczonych ram?
CodeGuy
1
@ reising1: W tym przypadku możesz także użyć metody dodawania NSString UIKit: sizeWithFont:constrainedToSize:lineBreakMode:Ale ten sposób jest trochę trudny
Martin Babacaev
6
Jest przestarzałe od iOS6. Zamień namyLabel.minimumScaleFactor:10.0/[UIFont labelFontSize];
Norbert,
72

minimumFontSizezostał wycofany z iOS 6. Możesz używać minimumScaleFactor.

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

Spowoduje to dostosowanie rozmiaru czcionki do szerokości etykiety i tekstu.

Amit Singh
źródło
Zwykle używam 0,8, ponieważ nawet 0,7 wydaje się zbyt małe. Oczywiście, niektóre teksty mogą nie pasować do minimalnego współczynnika skali 0,8, należy zdecydować, co wygląda lepiej, a gdzie coś staje się nieczytelne. OTOH moje aplikacje można obracać, co bardzo pomaga.
gnasher729
adjustsFontSizeToFitWidthzmniejsza tekst tylko wtedy, gdy nie mieści się w kontenerze
25
24

W oparciu o odpowiedź @Eyal Ben Dov możesz chcieć utworzyć kategorię, aby uczynić ją elastyczną w innych aplikacjach.

Obs .: Zaktualizowałem jego kod, aby był zgodny z iOS 7

Plik nagłówka

#import <UIKit/UIKit.h>

@interface UILabel (DynamicFontSize)

-(void) adjustFontSizeToFillItsContents;

@end

-Implementation file

#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

-Stosowanie

#import "UILabel+DynamicFontSize.h"

[myUILabel adjustFontSizeToFillItsContents];

Twoje zdrowie

Paulo Miguel Almeida
źródło
to nie działa dla mnie. Zawartość mojego UILabel jest teraz odcięta.
Adrian
1
Jeśli to nie działa, to prawdopodobnie dlatego, że ramka etykiety nie jest jeszcze ustawiona. Spróbuj ustawić ramkę przed wywołaniem tego (lub zadzwoń setNeedsLayout/ layoutIfNeededjeśli korzystasz z AutoLayout).
bmueller
Daje następującą awarię „” NSInvalidArgumentException ”, powód:„ NSConcreteAttributString initWithString :: zero wartość ””
Mohamed Saleh
Oznacza to, że twój NSString nie może być zerowy. Zakładam, że jeśli chcesz dostosować rozmiar czcionki, aby wypełnić zawartość UILabel, musisz przynajmniej podać tekst.
Paulo Miguel Almeida
Ma to wadę. Linia dzieli się między znakami, dzięki czemu słowa dzielą się na różne linie. Czy istnieje sposób na obejście tego?
Özgür,
24

Pojedyncza linia - są dwa sposoby, które możesz po prostu zmienić.

1- Pragmatycznie (Swift 3)

Wystarczy dodać następujący kod

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

2 - Korzystanie z inspektora atrybutów 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)

wprowadź opis zdjęcia tutaj

Devendra Singh
źródło
22

Jest 2015. Musiałem znaleźć post na blogu, który wyjaśniłby, jak to zrobić w najnowszej wersji iOS i XCode z Swift, aby działał z wieloma liniami.

  1. ustaw „Autoshrink” na „Minimalny rozmiar czcionki”.
  2. ustaw czcionkę na największy pożądany rozmiar (wybrałem 20)
  3. Zmień „Podziały linii” z „Zawijanie słów” na „Obcinaj ogon”.

Źródło: http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

zavtra
źródło
To naprawdę ratuje życie !!
bhakti123
2
Super fajnie .. Ten obcięty punkt ogona jest najważniejszy .. Ponieważ w przypadku zawijania tekstu autoukład nie odczuwa potrzeby zmniejszenia rozmiaru czcionki, natomiast gdy jest ścięty, autoukład musi zapisać tekst z ostrza i to oznacza to, że zmienia rozmiar czcionki.
GKK,
12

Wersja szybka:

textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Esqarrouth
źródło
Dzięki .. Wygląda na to, że sekwencja również ma znaczenie
Pramod
7

Oto rozszerzenie Swift dla UILabel. Działa algorytm wyszukiwania binarnego, aby zmienić rozmiar czcionki na podstawie szerokości i wysokości granic etykiety. Testowany do pracy z iOS 9 i autolayout.

SPOSÓB UŻYCIA: Gdzie <label>jest twój wstępnie zdefiniowany UILabel, który wymaga zmiany rozmiaru czcionki

<label>.fitFontForSize()

Domyślnie ta funkcja wyszukuje w zakresie rozmiarów czcionek 5pt i 300pt i ustawia czcionkę tak, aby „idealnie” mieściła się w tekście (w granicach 1,0pt). Można zdefiniować parametry, aby na przykład wyszukiwało między 1pt a bieżącym rozmiarem czcionki z dokładnością do 0,1 punktu w następujący sposób:

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

Skopiuj / Wklej następujący kod do swojego pliku

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
    }

}

UWAGA: Każde z layoutIfNeeded()połączeń można usunąć według własnego uznania

Avi Frankl
źródło
Ach - ale tak naprawdę to nie działa z autolayout; „sizeToFit” nic w tym przypadku nie robi.
Fattie
4

To trochę nie jest wyrafinowane, ale to powinno działać, na przykład powiedzmy, że chcesz ograniczyć swój uilabel do 120x120, przy maksymalnym rozmiarze czcionki 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
źródło
Wydaje się to raczej nieefektywne - należy pozwolić, aby UILabel dynamicznie sam się wyrównał, aby zmieścił się w pewnej dostępnej przestrzeni. Jeśli uruchomisz to dla czegoś takiego jak obliczanie czcionki tytułu komórki w widoku tabeli, wystąpią poważne problemy z opóźnieniem. Podejście to może działać, ale zdecydowanie nie jest zalecane.
Zorayr
Up-głosowanie za bycie jedyną osobą, która faktycznie odpowiedziała na pytanie.
Jai
2

Wystarczy wysłać wiadomość sizeToFit do UITextView. Dostosuje swoją wysokość, tak aby pasowała do tekstu. Nie zmieni własnej szerokości ani pochodzenia.

[textViewA1 sizeToFit];
codercat
źródło
Co się stanie, gdy rozmiar, który pasuje do tekstu, jest zbyt duży, aby zmieścić się w pojemniku? Na przykład, powiedzmy, że masz 100 możliwych punktów, aby pasowały do widoku tekstu, po wywołaniu sizeToFitlisty textViewA1staje się 200 punktów, które kończy się coraz przycięte.
Zorayr
0

Wersja 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
źródło
0

To rozwiązanie działa w przypadku multilinii:

Po przeczytaniu kilku artykułów i wymaganiu funkcji, która automatycznie skalowałaby tekst i dostosowywała liczbę wierszy do najlepiej dopasowanego do podanego rozmiaru etykiety, sam napisałem funkcję. (tzn. krótki sznurek dobrze pasowałby do jednej linii i używałby dużej ilości ramki etykiety, podczas gdy długi silny automatycznie dzieliłby się na 2 lub 3 linie i odpowiednio dostosowywałby rozmiar)

Możesz go ponownie użyć i dostosować w razie potrzeby. Pamiętaj, aby wywołać go po viewDidLayoutSubviewszakończeniu, aby ustawić początkową ramkę etykiety.

+ (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
źródło
0

Oto kod wypełnienia podklasy UILabel, która wprowadza animowaną zmianę rozmiaru czcionki:

@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];
}
wideolista
źródło