Mise en page automatique - la taille intrinsèque de UIButton n'inclut pas les encarts de titre


196

Si j'ai un UIButton arrangé en utilisant la mise en page automatique, sa taille s'ajuste bien pour s'adapter à son contenu.

Si je mets une image en tant que button.image, la taille intrinsèque semble à nouveau expliquer cela.

Cependant, si je modifie le titleEdgeInsetsbouton, la mise en page ne tient pas compte de cela et tronque le titre du bouton.

Comment puis-je m'assurer que la largeur intrinsèque du bouton représente l'encart?

entrez la description de l'image ici

Éditer:

J'utilise les éléments suivants:

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

Le but est d'ajouter une certaine séparation entre l'image et le texte.


3
L'avez-vous classé comme radar? Cela semble certainement être un bug dans les calculs de taille intrinsèque de l'UIButton.
Ryan Poolos

1
J'étais prêt à déposer un radar, mais cela semble en fait être un comportement attendu. Ceci est documenté sur les propriétés * EdgeInsets d'UIButton : "Les encarts que vous spécifiez sont appliqués au rectangle de titre après que ce rectangle a été dimensionné pour s'adapter au texte du bouton. Ainsi, des valeurs d'encart positives peuvent en fait couper le texte du titre. [...] Le bouton n'utilise pas cette propriété pour déterminer intrinsicContentSize et sizeThatFits: ".
Guillaume Algis

7
@GuillaumeAlgis Je dirais que bien que ce soit un comportement déclaré, ce n'est pas du tout ce à quoi on s'attendrait lors de l'utilisation de la mise en page automatique. J'ai déposé un bogue et j'encourage les autres à en déposer un également.
memmons

Si vous pouvez créer un lien vers le bug radar ici, pouvons-nous cliquer dessus et +1 dessus?
gprasant

1
de la titleEdgeInsetdocumentation: 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. Donc, en ajoutant un encart, vous forcez le bouton à couper le texte à coup sûr
Marco Pappalardo

Réponses:


192

Vous pouvez résoudre ce problème sans avoir à remplacer les méthodes ou à définir une contrainte de largeur arbitraire. Vous pouvez tout faire dans Interface Builder comme suit.

  • La largeur intrinsèque du bouton est dérivée de la largeur du titre plus la largeur de l'icône plus les encarts de bord de contenu gauche et droit .

  • Si un bouton contient à la fois une image et du texte, ils sont centrés en groupe, sans aucun remplissage entre eux.

  • Si vous ajoutez un encart de contenu gauche, il est calculé par rapport au texte, pas au texte + icône.

  • Si vous définissez une incrustation d'image gauche négative, l'image est tirée vers la gauche mais la largeur globale du bouton n'est pas affectée.

  • Si vous définissez un encart d'image gauche négatif, la mise en page réelle utilise la moitié de cette valeur. Donc, pour obtenir un encart gauche de -20 points, vous devez utiliser une valeur d'encart gauche de -40 points dans Interface Builder.

Vous fournissez donc un encart de contenu gauche suffisamment grand pour créer de l'espace pour l'encart gauche souhaité et le remplissage intérieur entre l'icône et le texte, puis déplacez l'icône vers la gauche en doublant la quantité de remplissage souhaitée entre l'icône et le texte. Le résultat est un bouton avec des encarts de contenu égaux à gauche et à droite, et une paire de texte et d'icônes qui sont centrés en tant que groupe, avec une quantité spécifique de remplissage entre eux.

Quelques exemples de valeurs:

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

pourquoi la mise en page réelle utilise la moitié de la valeur de l'encart gauche négatif ?? J'ai rencontré le même problème!
Tony Lin

1
C'est formidable qu'il existe une solution de contournement, mais j'espère que cela n'est pas utilisé pour justifier le comportement étrange de UIButton.
funct7

205

Vous pouvez le faire fonctionner dans Interface Builder (sans écrire de code), en utilisant une combinaison de titres et de contenus négatifs et positifs.

entrez la description de l'image ici

Mise à jour : Xcode 7 a un bug où vous ne pouvez pas entrer de valeurs négatives dans le Rightchamp Encart, mais vous pouvez utiliser le contrôle pas à pas à côté de lui pour diminuer la valeur. (Merci Stuart)

Faire cela ajoutera 8 pt d'espacement entre l'image et le titre et augmentera la largeur intrinsèque du bouton de la même quantité. Comme ça:

entrez la description de l'image ici


2
Il utilise contentEdgeInsets (qui n'est pas bogué) pour laisser la mise en page automatique augmenter la largeur du bouton. Et déplacez l'étiquette vers un espace vide à droite. Une façon astucieuse de contourner le bogue de l'encart du bord du titre.
Ugur

7
Cette astuce ne fonctionne plus. Le générateur d'interface n'accepte plus les valeurs négatives dans le Rightchamp.
Joris Mans

7
@JorisMans Vous ne pouvez pas saisir de valeurs négatives, mais cela a fonctionné pour moi en utilisant le contrôle pas à pas à droite du champ de texte pour descendre à la valeur négative requise ... allez comprendre!
Stuart

3
Cela devrait être la première réponse, pourquoi est-ce ici? J'ai essayé les 5 autres avant de trouver ça ...
Lord Zsolt

2
J'ai fait l'encart droit de contenu 16 pour centrer le texte dans UIButton
coolcool1994

96

Pourquoi ne pas remplacer la intrinsicContentSizeméthode sur UIView? Par exemple:

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

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

Cela devrait indiquer au système de mise en page automatique qu'il doit augmenter la taille du bouton pour permettre les encarts et afficher le texte intégral. Je ne suis pas sur mon propre ordinateur, je n'ai donc pas testé cela.


1
Pour autant que je sache, les boutons ne doivent pas être remplacés. Le problème est que chaque type de bouton est implémenté par une sous-classe différente.
Sulthan

2
intrinsicContentSizeest une méthode sur UIView, pas UIButton, donc vous ne feriez pas de bêtise avec les méthodes UIButton. Apple ne pense pas que ce soit un problème: "Remplacer cette méthode permet à une vue personnalisée de communiquer au système de mise en page quelle taille elle souhaite être basée sur son contenu." Et l'OP n'a rien dit sur les différents boutons, juste celui-là.
Maarten

1
Cela fonctionne certainement et c'est la solution que j'ai choisie. intrinsicContentSizeest en effet une méthode sur UIView et UIButton est une sous-classe de UIView donc vous pouvez bien sûr remplacer cette méthode; rien dans les documents d'Apple ne dit que vous ne devriez pas. Créez simplement une sous-classe UIButton en utilisant la méthode substituée de Maarten et modifiez votre UIButton dans Interface Builder pour qu'il soit de type YourUIButtonSubclass et cela fonctionnera parfaitement.
n8tr le

37
Il me semble que intrinsicContentSizeUIButton devrait ajouter dans le titleEdgeInsets, je vais déposer un bug avec Apple.
progrmr

6
Je suis d'accord, et la même chose pour imageEdgeInsets.
Ricardo Sanchez-Saez

87

Vous n'avez pas spécifié comment vous définissez les encarts, donc je suppose que vous utilisez titleEdgeInsets parce que je vois le même effet que vous obtenez. Si j'utilise contentEdgeInsets à la place, cela fonctionne correctement.

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

J'utilise en effet titleEdgeInsets. Je dois éloigner le titre de l'image, pas l'image du bord du bouton. Peut-être que je devrais simplement utiliser une image avec du rembourrage? Semble hacky cependant.
Ben Packard

Cela fonctionne parfaitement en combinaison avec la mise en page automatique, merci!
Cal S

3
C'est la meilleure solution, car elle fait exactement ce que vous voulez sans toucher à intrinsicContentSize.
RyJ

28
Cela ne répond PAS à la question lorsque vous utilisez une image et que vous devez ajuster l'encart entre l'image et le titre!
Brody Robertson du

23

Et pour Swift a travaillé ceci:

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
Même si vous n'êtes pas censé le faire, il est préférable de sous-classer dans ce cas car les documents Apple indiquent explicitement que la taille intrinsèque n'inclut pas titleEdgeInsets dans son calcul et donc en utilisant une extension, vous violez non seulement les attentes d'Apple, mais tous les autres développeurs qui lisent les documents.
Sirens

18

Ce fil est un peu ancien, mais je l'ai rencontré moi-même et j'ai pu le résoudre en utilisant un encart négatif. Par exemple, remplacez ici les valeurs de remplissage souhaitées:

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

Combiné avec les bonnes contraintes de mise en page automatique, vous vous retrouvez avec un bouton de redimensionnement automatique qui contient une image et du texte! Vu ci-dessous avec un desiredLeftPaddingréglage sur 10.

Bouton avec image et texte court

Bouton avec image et texte long

Vous pouvez voir que le cadre réel du bouton ne comprend pas l'étiquette (puisque l'étiquette est décalée de 10 points vers la droite, en dehors des limites), mais nous avons atteint 10 points de remplissage entre le texte et l'image.


1
C'est la solution que j'ai utilisée car elle ne nécessite pas de sous-classement. Ne fonctionnera pas si votre bouton a un arrière-plan, mais ce n'est généralement pas un problème avec iOS 7
José Manuel Sánchez

Cela fonctionnera avec une image d'arrière-plan si vous définissez également le décalage de contenu du bouton (valeur positive> = encart de titre).
Ben Flynn

9

Pour Swift 3 basé sur la réponse de 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)

    }

}

Bonjour. je veux utiliser le bouton d'extension personnalisé dans interfacebuilder. plz help
kemdo

6

Tout ce qui précède ne fonctionnait pas pour iOS 9+ , ce que j'ai fait est:

  • Ajouter une contrainte de largeur (pour une largeur minimale lorsque le bouton n'a pas de texte. Le bouton sera automatiquement mis à l'échelle si du texte est fourni)
  • définir la relation avec Supérieur ou égal à

entrez la description de l'image ici

Maintenant, pour ajouter une bordure autour du bouton, utilisez simplement la méthode:

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

Pourquoi pas? Il évolue automatiquement avec le contenu, il vous suffit de définir une largeur minimale (qui peut être plus petite que le texte à afficher)
Oritm

Parce que vous définissez une largeur minimale. L'idée entière de la mise en page automatique est de le faire sans définir de largeur explicite (minimale).
Joris Mans

Ce n'est pas à propos de la largeur, vous pouvez définir la largeur à 1 si vous préférez, mais la mise en page automatique doit savoir que la largeur peut être égale ou supérieure . J'ai mis à jour ma réponse
Oritm

Vous n'avez pas du tout besoin de la contrainte de largeur, le contentEdgeInset est la clé, la disposition automatique l'utilise ensuite pour la taille du contenu intrinsèque.
Chris Conover

5

Je voulais ajouter un espace de 5 points entre mon icône UIButton et l'étiquette. Voici comment je l'ai réalisé:

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

La façon dont contentEdgeInsets, titleEdgeInsets et imageEdgeInsets se rapportent les uns aux autres nécessite un peu de compromis de chaque encart. Donc, si vous ajoutez des encarts à gauche du titre, vous devez ajouter un encart négatif à droite et fournir plus d'espace (via un encart positif) à droite du contenu.

En ajoutant une insertion de contenu à droite pour correspondre au décalage des insertions de titre, mon texte ne sort pas des limites du bouton.


3

L'option est également disponible dans le générateur d'interface. Voir l'encart. J'ai mis à gauche et à droite à 3. Fonctionne comme un charme.

Capture d'écran du générateur d'interface


1
Oui, comme l' explique cette réponse , la raison pour laquelle cela fonctionne est que vous ajustez Edge: Content ici au lieu de Edge: Title ou Edge: Image .
smileyborg

1

La solution que j'utilise est d'ajouter une contrainte de largeur sur le bouton. Puis quelque part dans l'initialisation, une fois votre texte défini, mettez à jour la contrainte de largeur comme suit:

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

Où 8 correspond à votre encart.


Qu'est-ce que ButtonWidthConstraint?
Alexey Golikov


1
Ce n'est pas une excellente solution, car si la taille du contenu intrinsèque du bouton change, vous devez mettre à jour manuellement constantla contrainte de la nouvelle valeur ... et savoir quand la taille du contenu intrinsèque du bouton change est difficile sans sous-classer le bouton.
smileyborg

Ayup. Je n'utilise plus cette méthode. Surpris, il méritait un vote négatif mais ¯_ (ツ) _ / ¯
Bob Spryn

Un appel à setNeedsUpdateConstraintspeut être effectué "manuellement" après la mise à jour du titre ou de l'image du bouton. Vous pouvez ensuite remplacer updateConstraintset recalculer buttonWidthConstraintla constante de à partir de là. Ce n'est pas nécessairement la meilleure approche mais cela fonctionne assez bien pour moi. YMMV;)
Olivier

1

l'appel de sizeToFit () garantit que contentEdgeInset est pris en compte

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.