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.

Ben Packard
la source
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)
jaredsinclair
la source
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

n.Drake
la source
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.

Maarten
la source
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];
}
rdelmar
la source
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

cheville
la source
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.

Brian Gerstle
la source
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)

    }

}
TheoK
la source
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);
Oritm
la source
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.

orj
la source
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

Zeiteisen
la source
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.

Bob Spryn
la source
Qu'est-ce que ButtonWidthConstraint?
Alexey Golikov
@AlexeyGolikov An NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
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()
   }
}
Ali
la source