UILabel sizeToFit nie działa z automatycznym układem ios6

152

Jak mam skonfigurować programowo (iw jakiej metodzie) UILabel, którego wysokość zależy od jego tekstu? Próbowałem to skonfigurować za pomocą kombinacji Storyboard i kodu, ale bezskutecznie. Każdy poleca sizeToFitprzy ustawianiu lineBreakModei numberOfLines. Jednak bez względu na to, czy mogę umieścić ten kod viewDidLoad:, viewDidAppear:albo viewDidLayoutSubviewsnie mogę zmusić go do pracy. Albo robię pudełko za małe na długi tekst i nie rośnie, albo robię je za duże i się nie kurczy.

Circuitlego
źródło
FWIW ten sam kod: nie musiałem używać label.sizeToFit()w Xcode / viewController, ograniczenia były wystarczające. nie tworzył etykiety w Playground . Jak dotąd jedyny sposób, w jaki znalazłem to do pracy w Playground, to robićlabel.sizeToFit()
Kochanie,

Odpowiedzi:

407

Należy pamiętać, że w większości przypadków rozwiązanie Matta działa zgodnie z oczekiwaniami. Ale jeśli to nie zadziała, przeczytaj dalej.

Aby Twoja etykieta automatycznie zmieniała rozmiar, musisz wykonać następujące czynności:

  1. Ustaw ograniczenia układu dla etykiety
  2. Ustaw ograniczenie wysokości z niskim priorytetem. Powinien być niższy niż ContentCompressionResistancePriority
  3. Ustaw numberOfLines = 0
  4. Ustaw ContentHuggingPriority wyższy niż priorytet wysokości etykiety
  5. UstawferredMaxLayoutWidth dla etykiety. Ta wartość jest używana przez etykietę do obliczenia jej wysokości

Na przykład:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

Korzystanie z programu Interface Builder

  1. Ustaw cztery ograniczenia. Ograniczenie wysokości jest obowiązkowe. wprowadź opis obrazu tutaj

  2. Następnie przejdź do inspektora atrybutów etykiety i ustaw liczbę wierszy na 0. wprowadź opis obrazu tutaj

  3. Przejdź do inspektora rozmiaru etykiety i zwiększ pionową ContentHuggingPriority i pionową ContentCompressionResistancePriority.
    wprowadź opis obrazu tutaj

  4. Wybierz i edytuj ograniczenie wysokości.
    wprowadź opis obrazu tutaj

  5. I zmniejsz priorytet ograniczenia wysokości.
    wprowadź opis obrazu tutaj

Cieszyć się. :)

Mark Kryzhanouski
źródło
4
TAK! To jest dokładnie odpowiedź, której szukałem. Jedyne, co musiałem zrobić, to ustawić contentHuggingPriority i contentCompressionResistancePriority na required. Mógłbym to wszystko zrobić w IB! Dziękuję bardzo.
circuitlego
43
Zbyt dużo o tym myślisz. Ustaw preferredMaxLayoutWidthlub przypnij szerokość etykiety (lub jej prawą i lewą stronę). To wszystko! Będzie wtedy automatycznie rosnąć i kurczyć się w pionie, dopasowując się do zawartości.
mat
Musiałem ustawić właściwość PreferowanyMaxLayoutWidth + przypiąć szerokość etykiety. Działa jak urok :)
MartinMoizard
preferowaneMaxLayoutWidth i / lub przypinanie lewej i prawej strony nie jest bezwzględne. Priorytety przytulania treści i kompresji zawsze będą działać, o ile ich priorytety są wyższe niż pozostałe ograniczenia.
Eric Alford,
2
^ I w końcu dowiedziałem się, dlaczego, może warto wspomnieć, kiedy masz komórkę przyklejoną do dołu widoku, musisz ustawić priorytet ograniczenia „do dołu” na mniej niż 1000, ustawiłem go na 750 i wszystko działa idealnie. Myślę, że zmniejszyło to widok.
Mathijs Segers
65

W iOS 6, przy użyciu autolayout, jeśli boki (lub szerokość) i góra UILabel są przypięte, automatycznie powiększy się i zmniejszy w pionie, aby dopasować się do zawartości, bez żadnego kodu i bez bałaganu z jego odpornością na kompresję lub cokolwiek innego. To jest śmiertelnie proste.

W bardziej złożonych przypadkach wystarczy ustawić etykietę preferredMaxLayoutWidth.

Tak czy inaczej, właściwa rzecz dzieje się automatycznie.

matowe
źródło
12
Musisz również upewnić się, że ustawiłeś numberOfLines na 0, w przeciwnym razie nie otrzymasz zawijania.
devongovett
2
To mi nie wystarczało. Potrzebny był również punkt 2 z odpowiedzi @ Mark.
Danyal Aytekin,
czy możliwe jest również automatyczne powiększanie etykiety w kilku wierszach bez wpisywania kodów?
Noor
Jest to o wiele mniej skomplikowane niż przyjęta odpowiedź. Zrobiłem dwa przykłady ( tutaj i tutaj ) ilustrujące tę odpowiedź.
Suragch
@Suragch Używając ograniczeń to działa. Ale to nie działa dla tego samego kodu w Playground . Na placu zabaw musisz miećlabel.sizeToFit()
Honey
42

Chociaż pytanie brzmi programowo, napotkałem ten sam problem i wolę pracować w programie Interface Builder, pomyślałem, że przydatne może być dodanie do istniejących odpowiedzi za pomocą rozwiązania Interface Builder.

Pierwsza rzecz to zapomnieć sizeToFit. Auto Layout zajmie się tym w Twoim imieniu na podstawie wewnętrznego rozmiaru zawartości.

Problem polega zatem na tym, jak dopasować etykietę do zawartości za pomocą automatycznego układu? A konkretnie - bo o tym mowa w pytaniu - wysokość. Zauważ, że te same zasady odnoszą się do szerokości.

Zacznijmy więc od przykładu UILabel, który ma wysokość ustawioną na 41 pikseli:

wprowadź opis obrazu tutaj

Jak widać na powyższym zrzucie ekranu, "This is my text"ma dopełnienie powyżej i poniżej. To jest dopełnienie między wysokością UILabel a jego zawartością, tekstem.

Jeśli uruchomimy aplikację w symulatorze, na pewno zobaczymy to samo:

wprowadź opis obrazu tutaj

Teraz wybierzmy UILabel w Interface Builder i przyjrzyjmy się domyślnym ustawieniom w Inspektorze rozmiaru:

wprowadź opis obrazu tutaj

Zwróć uwagę na zaznaczone powyżej ograniczenie. To jest priorytet przytulania treści . Jak opisuje to Erica Sadun w doskonałym iOS Auto Layout Demystified , jest to:

sposób, w jaki widok woli unikać dodatkowego wypełnienia wokół swojej podstawowej treści

Dla nas, w przypadku UILabel, podstawową treścią jest tekst.

Tutaj dochodzimy do sedna tego podstawowego scenariusza. Nadaliśmy naszej etykiecie tekstowej dwa ograniczenia. Są w konflikcie. Mówi się, że „wysokość musi być równa wysokości 41 pikseli” . Drugi mówi „przytul widok do jego zawartości, aby nie było dodatkowego wypełnienia” . W naszym przypadku przytul widok do tekstu, aby nie było dodatkowego wypełnienia.

Teraz, dzięki automatycznemu układowi, z dwoma różnymi instrukcjami, które mówią, że rób różne rzeczy, środowisko wykonawcze musi wybrać jedną lub drugą. Nie może zrobić obu. UILabel nie może mieć jednocześnie 41 pikseli wysokości i nie może mieć wypełnienia.

Sposób rozwiązania tego problemu polega na określeniu priorytetu. Jedna instrukcja musi mieć wyższy priorytet niż druga. Jeśli obie instrukcje mówią różne rzeczy i mają ten sam priorytet, wystąpi wyjątek.

Więc spróbujmy. Moje ograniczenie wysokości ma priorytet 1000 , który jest wymagany . Wysokość przytulania treści to 250 , co jest słabe . Co się stanie, jeśli zmniejszymy priorytet ograniczenia wysokości do 249 ?

wprowadź opis obrazu tutaj

Teraz możemy zobaczyć, jak zaczyna się dziać magia. Spróbujmy na karcie SIM:

wprowadź opis obrazu tutaj

Niesamowite! Osiągnięto przytulanie treści. Tylko dlatego, że priorytet wysokości 249 jest mniejszy niż priorytet obejmowania treści 250 . Zasadniczo mówię, że „wysokość, którą tu podam, jest mniej ważna niż ta, którą określiłem dla przytulania treści” . Tak więc przytulanie treści wygrywa.

Podsumowując, dopasowanie etykiety do tekstu może być tak proste, jak określenie ograniczenia wysokości - lub szerokości - i prawidłowe ustawienie tego priorytetu w powiązaniu z ograniczeniem priorytetu obejmującym treść tej osi.

Pozostawi robienie odpowiednika szerokości jako ćwiczenie dla czytelnika!

Max MacLeod
źródło
3
Dziękuję bardzo za wspaniałe wyjaśnienie! Wreszcie rozumiem priorytet przytulania treści.
kernix
Czy możesz wyjaśnić, czym jest priorytet odporności na kompresję treści? Dzięki!
kernix
2
Doskonała odpowiedź Max! Czy jest jakiś sposób, aby ograniczyć liczbę linii działających w ten sposób?
rafaeljuzo
7

Zauważyłem, że w IOS7 sizeToFit również nie działał - być może rozwiązanie może ci pomóc

[textView sizeToFit];
[textView layoutIfNeeded];
Nick Wood
źródło
1
@Rambatino To mi pasuje. Dodaj, layoutIfNeededkiedy potrzebujesz setNeedsUpdateConstraints. Ale sytuacja może być trochę inna.
Gon
To działało najlepiej, gdy robiłem popover. Najpierw jednak musiałem zrobić layoutIfNeeded
Jason
4

Inna opcja zapewniająca synchronizację preferowanegoMaxLayoutWidth etykiety z szerokością etykiety:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Monte Hurd
źródło
4

Uważam, że powinienem wnieść swój wkład, ponieważ znalezienie odpowiedniego rozwiązania zajęło mi trochę czasu:

  • Celem jest umożliwienie Auto Layout pracy bez wywoływania sizeToFit (), zrobimy to, określając odpowiednie ograniczenia:
  • Określ górne, dolne i wiodące / końcowe ograniczenia spacji w UILabel
  • Ustaw właściwość number of lines na 0
  • Zwiększ priorytet przytulania treści do 1000
  • Zmniejsz priorytet odporności na kompresję zawartości do 500
  • Przy dolnym ograniczeniu kontenera obniż priorytet do 500

Zasadniczo dzieje się tak, że mówisz swojemu UILabel, że nawet jeśli ma on stałe ograniczenie wysokości, może on złamać to ograniczenie, aby zmniejszyć się, aby przytulić zawartość (na przykład, jeśli masz jedną linię), ale nie może przerwij wiązanie, aby je powiększyć.

ArturT
źródło
3

W moim przypadku tworzyłem podklasę UIView, która zawierała UILabel (o nieznanej długości). W iOS7 kod był prosty: ustaw ograniczenia, nie martw się o przytulanie treści lub odporność na kompresję i wszystko działało zgodnie z oczekiwaniami.

Ale w iOS6 UILabel był zawsze przycięty do jednej linii. Żadna z powyższych odpowiedzi nie pomogła. Ustawienia przytulania treści i odporności na kompresję zostały zignorowane. Jedynym rozwiązaniem, które zapobiegło przycinaniu, było umieszczenie na etykiecie preferowanegoMaxLayoutWidth. Ale nie wiedziałem, na jaką szerokość ustawić preferowaną szerokość, ponieważ rozmiar jego widoku nadrzędnego był nieznany (w rzeczywistości byłby definiowany przez zawartość).

W końcu znalazłem tutaj rozwiązanie . Ponieważ pracowałem nad widokiem niestandardowym, mogłem po prostu dodać następującą metodę, aby ustawić preferowaną szerokość układu po obliczeniu ograniczeń, a następnie ponownie je obliczyć:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
sumizome
źródło
To był dokładnie taki scenariusz, jaki miałem… czasami miło jest wiedzieć, że nie jesteś sam.
daveMac
Adam, walczę z tym, co powiedziałeś, że działa idealnie w ios7. Mam niestandardową komórkę z uiview i uilabel. Mogę dopasować etykietę do treści, ale widok nie, bez względu na to, jakie ograniczenia nałożyłem. Jak to działa? Przysięgam, próbowałem wszystkiego, ale nie zajmuje to wysokości etykiety
denikov
3

dodałem UILabel programowo iw moim przypadku wystarczyło:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
mixel
źródło
2

Rozwiązałem z xCode6, ustawiając „Preferowaną szerokość” na Automatycznie i przypinając etykietę na górze, na początku i na końcu wprowadź opis obrazu tutaj

Kazzar
źródło
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Swati
źródło
0

Natknąłem się również na ten problem z podklasą UIView, która zawiera UILabel jako jeden z elementów wewnętrznych. Nawet z automatycznym układem i wypróbowaniem wszystkich zalecanych rozwiązań etykieta po prostu nie zawężałaby wysokości do tekstu. W moim przypadku chcę, aby etykieta składała się tylko z jednej linii.

W każdym razie zadziałało dla mnie dodanie wymaganego ograniczenia wysokości dla UILabel i ustawienie go ręcznie na prawidłową wysokość po wywołaniu intrinsicContentSize. Jeśli nie masz UILabel zawartego w innym UIView, możesz spróbować utworzyć podklasę UILabel i zapewnić podobną implementację, najpierw ustawiając ograniczenie wysokości, a następnie zwracając
[super instrinsicContentSize]; zamiast [self.containerview intrinsiceContentSize]; tak jak poniżej, który jest specyficzny dla mojej podklasy UIView.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Działa teraz doskonale na iOS 7 i iOS 8.

n8tr
źródło
Warto zauważyć, że jeśli poprawnie skonfigurowałeś swój widok (tj. Twoje ograniczenia są poprawne i ustawiłeś wszystko na odpowiednich etapach cyklu życia widoku), nigdy nie powinieneś mieć nadpisywania intrinsicContentSize. Środowisko wykonawcze powinno być w stanie obliczyć to na podstawie poprawnie skonfigurowanych ograniczeń.
John Rogers,
Teoretycznie powinno to być poprawne, ale nie mamy dostępu do wszystkich prywatnych interfejsów API i implementacji Apple i nie są one niezawodne i zawierają błędy w kodzie.
n8tr
... ale [super intrinsicContentSize] powinien się tym zająć, tak właśnie mówię. Jeśli poprawnie zaimplementowałeś swój układ automatyczny.
John Rogers
0

Rozwiązanie, które zadziałało dla mnie; Jeśli twój UILabel ma stałą szerokość, zmień ograniczenie z constant =na constant <=w pliku interfejsu

wprowadź opis obrazu tutaj

Alex Brown
źródło
-1

W moim przypadku, gdy używam etykiet w UITableViewCell, etykieta zmieniłaby rozmiar, ale wysokość przekroczyłaby wysokość komórki tabeli. To właśnie zadziałało dla mnie. Zrobiłem zgodnie z Max MacLeod, a następnie upewniłem się, że wysokość komórki została ustawiona na UITableViewAutomaticDimension.

Możesz dodać to w swoim init lub wakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

W serii ujęć wybierz komórkę, otwórz Inspektora rozmiaru i upewnij się, że wysokość wiersza jest ustawiona na „Domyślna”, odznaczając pole wyboru „Niestandardowe”.

W wersji 8.0 występuje problem, który wymaga również ustawienia w kodzie.

Maria
źródło