Autolayout: la dimensione intrinseca di UIButton non include gli inserimenti del titolo


196

Se ho un UIButton organizzato usando il layout automatico, le sue dimensioni si adattano perfettamente al contenuto.

Se imposto un'immagine come button.image , le dimensioni dell'istruttore sembrano giustificare nuovamente questo.

Tuttavia, se modifico il titleEdgeInsets pulsante, il layout non tiene conto di questo e tronca invece il titolo del pulsante.

Come posso garantire che la larghezza intrinseca del pulsante sia responsabile dell'inserzione?

inserisci qui la descrizione dell'immagine

Modificare:

Sto usando il seguente:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

L'obiettivo è quello di aggiungere una certa separazione tra l'immagine e il testo.


3
Hai archiviato questo come radar? Sembra certamente essere un bug nei calcoli delle dimensioni intrinseche di UIButton.
Ryan Poolos,

1
Ero pronto a presentare un radar, ma questo in realtà sembra essere un comportamento previsto. Questo è documentato sulle proprietà * EdgeInsets di UIButton : "Gli inserti specificati vengono applicati al rettangolo del titolo dopo che quel rettangolo è stato dimensionato per adattarsi al testo del pulsante. Pertanto, i valori degli inserti positivi possono effettivamente tagliare il testo del titolo. [...] Il pulsante non utilizza questa proprietà per determinare intrinsicContentSize e sizeThatFits :. "
Guillaume Algis,

7
@GuillaumeAlgis Direi che anche se questo è un comportamento dichiarato, è non è affatto quello che ci si aspetta che accada quando si utilizza autolayout. Ho archiviato un bug e incoraggerei anche altri a presentarne uno.
Memmons

Se puoi collegarti al bug del radar qui, possiamo fare clic su di esso e fare +1 su di esso?
gprasant,

1
dalla titleEdgeInsetdocumentazione: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text. quindi aggiungendo l'inserzione si sta forzando il pulsante per tagliare il testo di sicuro
Marco Pappalardo,

Risposte:


192

Puoi risolverlo senza dover sostituire alcun metodo o impostare un vincolo di larghezza arbitrario. Puoi fare tutto in Interface Builder come segue.

  • La larghezza intrinseca del pulsante è derivata dalla larghezza del titolo più la larghezza dell'icona più le inserzioni dei bordi del contenuto sinistro e destro .

  • Se un pulsante ha sia un'immagine che un testo, sono centrati come un gruppo, senza riempimento.

  • Se aggiungi un inserto di contenuto sinistro, viene calcolato in relazione al testo, non al testo + icona.

  • Se si imposta un inserto di immagine a sinistra negativo, l'immagine viene estratta a sinistra ma la larghezza complessiva del pulsante non viene modificata.

  • Se si imposta un inserto di immagine a sinistra negativo, il layout effettivo utilizza metà di quel valore. Quindi per ottenere un inserto a sinistra di -20 punti, è necessario utilizzare un valore di inserzione a sinistra di -40 punti in Interface Builder.

Quindi fornisci un inserto di contenuto sinistro abbastanza grande da creare spazio sia per l'inserto sinistro desiderato che per l'imbottitura interna tra l'icona e il testo, e quindi spostare l'icona di sinistra raddoppiando la quantità di imbottitura che si desidera tra l'icona e il testo. Il risultato è un pulsante con uguali inserimenti di contenuto sinistro e destro e una coppia di testo e icona centrata come un gruppo, con una specifica quantità di riempimento tra di loro.

Alcuni valori di esempio:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)

perché il layout effettivo utilizza la metà del valore dell'inserzione sinistra negativa ?? Ho riscontrato lo stesso problema!
Tony Lin,

1
È fantastico che ci sia una soluzione alternativa, ma spero che questo non sia usato per giustificare lo strano comportamento di UIButton.
funct7

205

Puoi farlo funzionare in Interface Builder (senza scrivere alcun codice), usando una combinazione di titoli e inserimenti di contenuti negativi e positivi.

inserisci qui la descrizione dell'immagine

Aggiornamento : Xcode 7 ha un bug in cui non è possibile inserire valori negativi nel Rightcampo Inset, ma è possibile utilizzare il controllo stepper accanto ad esso per ridurre il valore. (Grazie Stuart)

In questo modo si aggiungono 8pt di spaziatura tra l'immagine e il titolo e si aumenta la larghezza intrinseca del pulsante della stessa quantità. Come questo:

inserisci qui la descrizione dell'immagine


2
Sta usando contentEdgeInsets (che non è buggy) per consentire il layout automatico per aumentare la larghezza del pulsante. E sposta l'etichetta nello spazio vuoto a destra. Modo intelligente per aggirare il bug dell'inserzione del bordo del titolo.
ug

7
Questo trucco non funziona più. Il builder di interfaccia non accetta più valori negativi nel Rightcampo.
Joris Mans,

7
@JorisMans Non è possibile digitare valori negativi in, ma ha funzionato per me utilizzando il controllo passo-passo a destra del campo di testo di dimettersi al valore negativo richiesta ... figura Go!
Stuart,

3
Questa dovrebbe essere la prima risposta, perché è qui? Ho provato gli altri 5 prima di trovare questo ...
Lord Zsolt,

2
Ho inserito il diritto di inserimento dei contenuti 16 per centrare il testo in UIButton
coolcool1994,

96

Perché non sovrascrivere il intrinsicContentSizemetodo su UIView? Per esempio:

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

Questo dovrebbe dire al sistema di autolayout che dovrebbe aumentare le dimensioni del pulsante per consentire l'inserzione e mostrare il testo completo. Non sono sul mio computer, quindi non l'ho provato.


1
I pulsanti non dovrebbero essere ignorati per quanto ne so. Il problema è che ogni tipo di pulsante è implementato da una sottoclasse diversa.
Sulthan,

2
intrinsicContentSizeè un metodo su UIView, non su UIButton, quindi non dovresti fare scherzi in nessun metodo UIButton. Apple non ritiene che si tratti di un problema: "Sostituire questo metodo consente a una vista personalizzata di comunicare al sistema di layout le dimensioni che desidera basarsi sul suo contenuto". E l'OP non ha detto nulla sui diversi pulsanti, solo quello.
Maarten,

1
Questo sicuramente funziona ed è la soluzione con cui sono andato. intrinsicContentSizeè effettivamente un metodo su UIView e UIButton è una sottoclasse di UIView, quindi ovviamente puoi ignorare questo metodo; nulla nei documenti di Apple dice che non dovresti. Basta creare una sottoclasse UIButton utilizzando il metodo ignorato di Maarten e modificare UIButton in Interface Builder in modo che sia di tipo YourUIButtonSubclass e funzionerà perfettamente.
n.

37
Mi sembra che intrinsicContentSizeUIButton dovrebbe aggiungere il titoloEdgeInsets, ho intenzione di presentare un bug con Apple.
programma

6
Sono d'accordo, e lo stesso per imageEdgeInsets.
Ricardo Sanchez-Saez,

87

Non hai specificato come stai impostando gli inserti, quindi immagino che stai usando titleEdgeInsets perché vedo lo stesso effetto che stai ottenendo. Se uso contentEdgeInsets invece funziona correttamente.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}

Sto davvero usando titleEdgeInsets. Devo distanziare il titolo dall'immagine, non l'immagine dal bordo del pulsante. Forse dovrei solo usare un'immagine con un po 'di imbottitura? Sembra schifoso però.
Ben Packard,

Funziona perfettamente in combinazione con il layout automatico, grazie!
Cal S

3
Questa è la soluzione migliore, poiché fa esattamente quello che vuoi senza toccare intrinsicContentSize.
RyJ,

28
Questo NON risponde alla domanda quando si utilizza un'immagine e è necessario regolare l'inserzione tra l'immagine e il titolo!
Brody Robertson

23

E per Swift ha funzionato così:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

Love U Swift


1
Anche se non dovresti, in questo caso è meglio effettuare una sottoclasse perché i documenti Apple dichiarano esplicitamente che la dimensione intrinseca non include titleEdgeInsets nel suo calcolo e quindi usando un'estensione stai violando non solo le aspettative di Apple ma tutti gli altri sviluppatori che leggono i documenti.
Sirene,

18

Questo thread è un po 'vecchio, ma mi sono appena imbattuto in questo me stesso e sono stato in grado di risolverlo utilizzando un inserto negativo. Ad esempio, sostituisci i valori di imbottitura desiderati qui:

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

Combinato con i giusti vincoli di autolayout, si finisce con un pulsante di ridimensionamento automatico che contiene un'immagine e testo! Visto di seguito con desiredLeftPaddingimpostato su 10.

Pulsante con immagine e testo breve

Pulsante con immagine e testo lungo

Puoi vedere che la cornice effettiva del pulsante non comprende l'etichetta (poiché l'etichetta viene spostata di 10 punti a destra, fuori dai limiti), ma abbiamo ottenuto 10 punti di riempimento tra il testo e l'immagine.


1
Questa è la soluzione che ho usato in quanto non richiede la sottoclasse. Non funzionerà se il tuo pulsante ha uno sfondo, ma di solito non è un problema con iOS 7
José Manuel Sánchez

Funzionerà con un'immagine di sfondo se imposti anche l'offset del contenuto del pulsante (valore positivo> = inserto titolo).
Ben Flynn,

9

Per Swift 3 basato sulla risposta di Pegpeg :

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}

Ciao. Voglio usare il pulsante di estensione personalizzato in Interfacebuilder. per favore aiuto
kemdo,

6

Tutto sopra non ha funzionato per iOS 9+ , quello che ho fatto è:

  • Aggiungi un vincolo di larghezza (per una larghezza minima quando il pulsante non ha testo. Il pulsante si ridimensionerà automaticamente se viene fornito il testo)
  • imposta la relazione su Maggiore o uguale

inserisci qui la descrizione dell'immagine

Ora per aggiungere un bordo attorno al pulsante basta usare il metodo:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);

Perchè no? Si ridimensiona automaticamente con il contenuto, devi solo impostare una larghezza minima (che può essere più piccola del testo da visualizzare)
Oritm,

Perché si definisce una larghezza minima. L'intera idea di autolayout è fatta senza impostare alcuna larghezza esplicita (minima).
Joris Mans,

Non si tratta della larghezza, è possibile impostare la larghezza su 1 se si preferisce, ma il completamento automatico deve sapere che la larghezza può essere uguale o maggiore . Ho aggiornato la mia risposta
Oritm

Non è necessario il vincolo di larghezza, contentEdgeInset è la chiave, il layout automatico lo utilizza quindi per le dimensioni intrinseche del contenuto.
Chris Conover,

5

Volevo aggiungere uno spazio 5pt tra la mia icona UIButton e l'etichetta. Ecco come l'ho raggiunto:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

Il modo in cui contentEdgeInsets, titleEdgeInsets e imageEdgeInsets si relazionano tra loro richiede un piccolo dare e avere da ogni inserto. Quindi, se aggiungi alcuni inserti a sinistra del titolo, devi aggiungere un inserto negativo a destra e fornire un po 'più di spazio (tramite un inserto positivo) sul contenuto a destra.

Aggiungendo un giusto inserto di contenuto per adattarsi allo spostamento delle inserzioni del titolo, il mio testo non va oltre i limiti del pulsante.


3

L'opzione è disponibile anche nel generatore di interfaccia. Vedi l'inserto. Ho impostato sinistra e destra su 3. Funziona come un fascino.

Schermata del generatore di interfaccia


1
Sì, come spiega questa risposta , il motivo per cui funziona è perché stai regolando Edge: Contenuto qui invece di Edge: Titolo o Edge: Immagine .
smileyborg,

1

La soluzione che uso è aggiungere un vincolo di larghezza al pulsante. Quindi da qualche parte nell'inizializzazione, dopo aver impostato il testo, aggiorna il vincolo di larghezza in questo modo:

self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;

Dove 8 è qualunque sia la tua inserzione.


Che cos'è buttonWidthConstraint?
Alexey Golikov,


1
Questa non è un'ottima soluzione, perché se la dimensione del contenuto intrinseco del pulsante cambia, dovresti aggiornare manualmente constantil vincolo al nuovo valore ... e sapere quando cambia la dimensione del contenuto intrinseco del pulsante senza sottoclasse del pulsante.
smileyborg,

Ayup. Non uso più questo metodo. Sorpreso, è stato degno di un voto negativo ma ¯ _ (ツ) _ / ¯
Bob Spryn,

Una chiamata a setNeedsUpdateConstraintspuò essere "manualmente" effettuata dopo aver aggiornato il titolo del pulsante o l'immagine. È quindi possibile sovrascrivere updateConstraintse ricalcolare buttonWidthConstraintla costante da lì. Questo non è necessariamente l'approccio migliore, ma funziona abbastanza bene per me. YMMV;)
Olivier il

1

chiamando sizeToFit () si assicura che contentEdgeInset sia attivo

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
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.