UILabel sizeToFit non funziona con il layout automatico ios6


152

Come posso configurare in modo programmatico (e in quale metodo) un UILabel la cui altezza dipende dal suo testo? Ho provato a configurarlo usando una combinazione di Storyboard e codice, ma senza risultati. Tutti raccomandano sizeToFitdurante l'impostazione lineBreakModee numberOfLines. Tuttavia, non importa se ho messo quel codice in viewDidLoad:, viewDidAppear:o viewDidLayoutSubviewsnon riesco a farlo funzionare. O rendo la casella troppo piccola per il testo lungo e non cresce, oppure la ingrandisco troppo e non si restringe.


FWIW lo stesso codice: non avevo bisogno di usare label.sizeToFit()in Xcode / viewController, i vincoli erano sufficienti. non stava creando l'etichetta in Playground . Finora l'unico modo in cui l'ho trovato funzionare nel parco giochi è quello di farlolabel.sizeToFit()
Honey

Risposte:


407

Si noti che nella maggior parte dei casi la soluzione di Matt funziona come previsto. Ma se non funziona per te, per favore, leggi oltre.

Per fare in modo che la tua etichetta ridimensioni automaticamente l'altezza, devi fare quanto segue:

  1. Imposta i vincoli di layout per l'etichetta
  2. Impostare il vincolo di altezza con bassa priorità. Dovrebbe essere inferiore a ContentCompressionResistancePriority
  3. Imposta numberOfLines = 0
  4. Impostare ContentHuggingPriority su un valore più alto rispetto alla priorità dell'altezza dell'etichetta
  5. Impostare favoriteMaxLayoutWidth per l'etichetta. Tale valore viene utilizzato dall'etichetta per calcolare la sua altezza

Per esempio:

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_)]];

Utilizzando Interface Builder

  1. Imposta quattro vincoli. Il vincolo di altezza è obbligatorio. inserisci qui la descrizione dell'immagine

  2. Quindi vai alla finestra di ispezione degli attributi dell'etichetta e imposta il numero di righe su 0. inserisci qui la descrizione dell'immagine

  3. Vai alla finestra di ispezione delle dimensioni dell'etichetta e aumenta ContentHuggingPriority verticale e ContentCompressionResistancePriority verticale.
    inserisci qui la descrizione dell'immagine

  4. Seleziona e modifica il vincolo di altezza.
    inserisci qui la descrizione dell'immagine

  5. E ridurre la priorità del vincolo di altezza.
    inserisci qui la descrizione dell'immagine

Godere. :)


4
SÌ! Questa è esattamente la risposta che stavo cercando. L'unica cosa che dovevo fare era impostare contentHuggingPriority e contentCompressionResistancePriority su richiesto. Potrei fare tutto in IB! Grazie mille.
circuitlego,

43
Stai pensando troppo a questo. Impostare preferredMaxLayoutWidtho appuntare la larghezza dell'etichetta (o i suoi lati destro e sinistro). È tutto! Quindi crescerà automaticamente e si ridurrà verticalmente per adattarsi al suo contenuto.
matt

Ho dovuto impostare la proprietà favoriteMaxLayoutWidth + bloccare la larghezza dell'etichetta. Funziona come un fascino :)
MartinMoizard,

favoriteMaxLayoutWidth e / o bloccare i lati sinistro e destro non è assoluto. Le priorità di compressione e abbraccio dei contenuti funzioneranno sempre fintanto che le loro priorità saranno più elevate rispetto al resto dei vincoli.
Eric Alford,

2
^ E finalmente ho scoperto perché, potrebbe valere la pena menzionare, quando la cella si attacca al fondo della vista, è necessario impostare la priorità del vincolo "verso il basso" su meno di 1000, l'ho messo su 750 e tutto funziona perfettamente. Immagino che abbia ridotto la vista.
Mathijs Segers,

65

In iOS 6, utilizzando il layout automatico, se i lati (o la larghezza) di UILabel e la parte superiore sono bloccati, crescerà automaticamente e si ridurrà verticalmente per adattarsi al suo contenuto, senza codice e senza problemi con la sua resistenza alla compressione o altro. È morto semplice.

In casi più complessi, basta impostare l'etichetta preferredMaxLayoutWidth.

Ad ogni modo, la cosa giusta accade automaticamente.


12
Devi anche assicurarti di impostare numberOfLines su 0 altrimenti non otterrai il wrapping.
devongovett,

2
Questo non era abbastanza per me. Necessario anche il punto 2 della risposta di @ Mark.
Danyal Aytekin,

è anche possibile far crescere automaticamente l'etichetta su più righe senza scrivere codici?
Noor,

Questo è molto meno complicato della risposta accettata. Ho fatto due esempi ( qui e qui ) che illustrano questa risposta.
Suragch

@Suragch Usando i vincoli funziona. Ma non funziona per lo stesso codice in Playground . Nel parco giochi devi averelabel.sizeToFit()
Honey

42

Sebbene la domanda sia programmatica, avendo riscontrato lo stesso problema e preferendo lavorare in Interface Builder, ho pensato che potesse essere utile aggiungere le risposte esistenti con una soluzione Interface Builder.

La prima cosa è dimenticare sizeToFit. Auto Layout gestirà questo per tuo conto in base alle dimensioni intrinseche del contenuto.

Il problema quindi è, come ottenere un'etichetta adatta al suo contenuto con Auto Layout? In particolare, perché la domanda lo menziona, l'altezza. Si noti che gli stessi principi si applicano alla larghezza.

Quindi iniziamo con un UILabel di esempio che ha un'altezza impostata su 41px di altezza:

inserisci qui la descrizione dell'immagine

Come puoi vedere nella schermata qui sopra, "This is my text"ha un'imbottitura sopra e sotto. Questa è un'imbottitura tra l'altezza di UILabel, ed è il contenuto, il testo.

Se eseguiamo l'app nel simulatore, abbastanza sicuro, vediamo la stessa cosa:

inserisci qui la descrizione dell'immagine

Ora, selezioniamo UILabel in Interface Builder e diamo un'occhiata alle impostazioni predefinite nella finestra di ispezione Dimensione:

inserisci qui la descrizione dell'immagine

Nota il vincolo evidenziato sopra. Questa è la priorità di abbracciare il contenuto . Come descrive Erica Sadun nell'eccellente layout automatico di iOS Demysified , questo è:

il modo in cui una vista preferisce evitare un'ulteriore imbottitura attorno al suo contenuto principale

Per noi, con UILabel, il contenuto principale è il testo.

Qui veniamo al cuore di questo scenario di base. Abbiamo dato alla nostra etichetta di testo due vincoli. Sono in conflitto. Uno dice "l'altezza deve essere uguale a 41 pixel di altezza" . L'altro dice "Abbraccia la vista sul suo contenuto, quindi non abbiamo alcuna imbottitura extra" . Nel nostro caso, abbraccia la vista al suo testo in modo da non avere alcuna imbottitura aggiuntiva.

Ora, con Auto Layout, con due diverse istruzioni che dicono di fare cose diverse, il runtime deve scegliere l'uno o l'altro. Non può fare entrambe le cose. UILabel non può essere alto entrambi 41 pixel e non ha imbottitura.

Il modo in cui questo viene risolto è specificando la priorità. Un'istruzione deve avere una priorità più alta dell'altra. Se entrambe le istruzioni dicono cose diverse e hanno la stessa priorità, si verificherà un'eccezione.

Quindi proviamo. Il mio vincolo di altezza ha una priorità di 1000 , che è richiesto . L'altezza dell'abbraccio del contenuto è 250 , che è debole . Cosa succede se riduciamo la priorità del vincolo di altezza a 249 ?

inserisci qui la descrizione dell'immagine

Ora possiamo vedere l'inizio della magia. Proviamo nella sim:

inserisci qui la descrizione dell'immagine

Eccezionale! Abbraccio dei contenuti raggiunto. Solo perché la priorità di altezza 249 è inferiore alla priorità di abbraccio del contenuto 250 . Fondamentalmente, sto dicendo "l'altezza qui specificata è meno importante di quella che ho specificato per l'abbraccio del contenuto" . Quindi, l'abbraccio del contenuto vince.

In conclusione, far sì che l'etichetta si adatti al testo può essere semplice quanto specificare il vincolo di altezza - o larghezza - e correggere l'impostazione di quella priorità in associazione con il vincolo di priorità di abbraccio del contenuto di quell'asse.

Lascerà fare l'equivalente della larghezza come esercizio per il lettore!


3
Grazie mille per la tua grande spiegazione! Finalmente capisco la priorità di abbracciare i contenuti.
Kernix,

Potresti spiegare qual è anche la priorità di resistenza alla compressione dei contenuti? Grazie!
Kernix,

2
Excelente rispondi Max! Esiste un modo per limitare il numero di linee che funzionano in questo modo?
rafaeljuzo,

7

Notato nella dimensione IOS7 Anche ToFit non funzionava - forse la soluzione potrebbe aiutarti anche tu

[textView sizeToFit];
[textView layoutIfNeeded];

1
@Rambatino Questo funziona per me. Aggiungi layoutIfNeededquando ne hai bisogno setNeedsUpdateConstraints. Ma la situazione potrebbe essere leggermente diversa.
Andato il

Questo ha funzionato meglio per me quando ho fatto una sosta. Avevo bisogno di fare il layout Se avessi bisogno prima di tutto
Jason

4

Un'altra opzione per garantire che l'etichetta preferita MaxLayoutWidth dell'etichetta sia mantenuta sincronizzata con la larghezza dell'etichetta:

#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

4

Sento che dovrei contribuire perché mi ci è voluto un po 'di tempo per trovare la soluzione giusta:

  • L'obiettivo è lasciare che Auto Layout faccia il suo lavoro senza mai chiamare sizeToFit (), lo faremo specificando i giusti vincoli:
  • Specifica i vincoli di spazio superiore, inferiore e iniziale / finale su UILabel
  • Impostare la proprietà numero di righe su 0
  • Aumenta la priorità di abbracciamento del contenuto a 1000
  • Abbassa la priorità di resistenza alla compressione del contenuto a 500
  • Sul vincolo del contenitore inferiore, ridurre la priorità a 500

Fondamentalmente, ciò che accade è che dici a UILabel che anche se ha un vincolo di altezza fisso, può fare in modo che si rompa il vincolo per ridurlo al fine di abbracciare il contenuto (se hai una sola riga per esempio), ma non può rompere il vincolo per ingrandirlo.


3

Nel mio caso stavo creando una sottoclasse UIView che conteneva un UILabel (di lunghezza sconosciuta). In iOS7 il codice era semplice: imposta i vincoli, non preoccuparti dell'abbraccio dei contenuti o della resistenza alla compressione e tutto ha funzionato come previsto.

Ma in iOS6 UILabel è sempre stato troncato su una sola riga. Nessuna delle risposte sopra ha funzionato per me. Le impostazioni di abbracciamento del contenuto e resistenza alla compressione sono state ignorate. L'unica soluzione che impediva il clipping era quella di includere un preferitoMaxLayoutWidth sull'etichetta. Ma non sapevo su cosa impostare la larghezza preferita, poiché la dimensione della sua vista padre era sconosciuta (in effetti, sarebbe stata definita dal contenuto).

Finalmente ho trovato la soluzione qui . Poiché stavo lavorando su una vista personalizzata, potevo semplicemente aggiungere il seguente metodo per impostare la larghezza del layout preferita dopo che i vincoli erano stati calcolati una volta, quindi ricalcolarli:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}

Questo era lo scenario esatto che avevo ... a volte è bello sapere che non sei solo.
daveMac,

Adam, sto lottando con quello che hai detto ha funzionato perfettamente in iOS7. Ho una cella personalizzata con uiview e uilabel. Riesco a far sì che l'etichetta si adatti al contenuto, ma la vista non lo fa, indipendentemente dai vincoli che ci ho messo. Come hai fatto a farlo funzionare? Giuro di aver provato di tutto ma non ci vuole l'altezza dell'etichetta
denikov,

3

Ho aggiunto a livello di UILabelcodice e nel mio caso è stato sufficiente:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0

2

ho risolto con xCode6 mettendo "Larghezza preferita" su Automatico e appuntando la parte superiore dell'etichetta, in testa e in coda inserisci qui la descrizione dell'immagine


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));

0

Ho riscontrato questo problema anche con una sottoclasse UIView che contiene un UILabel come uno se i suoi elementi interni. Anche con autolayout e provando tutte le soluzioni consigliate, l'etichetta non restringerebbe l'altezza al testo. Nel mio caso, desidero sempre che l'etichetta sia una riga.

Comunque, ciò che ha funzionato per me è stato aggiungere un vincolo di altezza richiesto per UILabel e impostarlo manualmente all'altezza corretta quando viene chiamato intrinsicContentSize. Se UILabel non è contenuto in un'altra UIView, puoi provare a sottoclassare UILabel e fornire un'implementazione simile impostando prima il vincolo di altezza e poi restituendo
[super instrinsicContentSize]; anziché [self.containerview intrinsiceContentSize]; come faccio di seguito, che è specifico per la mia sottoclasse UIView.

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

}

Funziona perfettamente ora su iOS 7 e iOS 8.


Vale la pena notare che se hai impostato correttamente la tua vista (cioè i tuoi vincoli sono corretti e hai impostato tutto durante le fasi corrette nel ciclo di vita della vista), non dovresti mai scavalcare intrinsicContentSize. Il runtime dovrebbe essere in grado di calcolare questo in base ai vincoli di configurazione corretti.
John Rogers,

In teoria ciò dovrebbe essere corretto, ma non abbiamo accesso a tutte le API e le implementazioni private di Apple e non sono infallibili e hanno dei bug nel loro codice.
n8tr

... ma [super intrinsicContentSize] dovrebbe occuparsene, è quello che sto dicendo. Se hai implementato correttamente il layout automatico.
John Rogers,

0

Una soluzione che ha funzionato per me; Se il vostro UILabel ha una larghezza fissa, modificare il vincolo da constant =a constant <=nel file di interfaccia

inserisci qui la descrizione dell'immagine


-1

Nel mio caso quando si usano le etichette in un UITableViewCell, l'etichetta in si ridimensionerebbe ma l'altezza avrebbe superato l'altezza della cella della tabella. Questo è ciò che ha funzionato per me. Ho fatto secondo Max MacLeod, e poi mi sono assicurato che l'altezza della cella fosse impostata su UITableViewAutomaticDimension.

Puoi aggiungere questo è nel tuo init o awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

Nello storyboard, seleziona la cella, apri la finestra di ispezione Dimensione e assicurati che l'altezza della riga sia impostata su "Predefinito" selezionando la casella di controllo "Personalizzato".

C'è un problema in 8.0 che richiede anche che sia impostato nel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.