UIButton: comment centrer une image et un texte en utilisant imageEdgeInsets et titleEdgeInsets?

159

Si je mets seulement une image dans un bouton et que je mets imageEdgeInsets plus près du haut, l'image reste centrée et tout fonctionne comme prévu:

[button setImage:image forState:UIControlStateNormal];
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, 0.0)];

Si je mets seulement un texte dans un bouton et que je définis titleEdgeInsets plus près du bas, le texte reste centré et tout fonctionne comme prévu:

[button setTitle:title forState:UIControlStateNormal];
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, 0.0, -30, 0.0)];

Mais, si je mets les 4 lignes ensemble, le texte interfère avec l'image et les deux perdent l'alignement central.

Toutes mes images ont une largeur de 30 pixels, et si je mets 30 dans le paramètre gauche de UIEdgeInsetMake pour setTitleEdgeInsets, le texte est à nouveau centré. Le problème est que l'image n'est jamais centrée car il semble qu'elle dépend de la taille de button.titleLabel. J'ai déjà essayé de nombreux calculs avec la taille du bouton, la taille de l'image, la taille de titleLabel et je n'ai jamais été parfaitement centré.

Quelqu'un a déjà eu le même problème?

Reinaldoluckman
la source

Réponses:

412

Pour ce que ça vaut, voici une solution générale pour positionner l'image centrée au-dessus du texte sans utiliser de nombres magiques. Notez que le code suivant est obsolète et que vous devriez probablement utiliser l'une des versions mises à jour ci - dessous :

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.frame.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = button.titleLabel.frame.size;
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

La version suivante contient des modifications pour prendre en charge iOS 7+ qui ont été recommandées dans les commentaires ci-dessous. Je n'ai pas testé ce code moi-même, donc je ne suis pas sûr de son bon fonctionnement ou de sa rupture s'il était utilisé sous les versions précédentes d'iOS.

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.image.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: button.titleLabel.font}];
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

// increase the content height to avoid clipping
CGFloat edgeOffset = fabsf(titleSize.height - imageSize.height) / 2.0;
button.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0);

Version Swift 5.0

extension UIButton {
  func alignVertical(spacing: CGFloat = 6.0) {
    guard let imageSize = imageView?.image?.size,
      let text = titleLabel?.text,
      let font = titleLabel?.font
    else { return }

    titleEdgeInsets = UIEdgeInsets(
      top: 0.0,
      left: -imageSize.width,
      bottom: -(imageSize.height + spacing),
      right: 0.0
    )

    let titleSize = text.size(withAttributes: [.font: font])
    imageEdgeInsets = UIEdgeInsets(
      top: -(titleSize.height + spacing),
      left: 0.0,
      bottom: 0.0, right: -titleSize.width
    )

    let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0
    contentEdgeInsets = UIEdgeInsets(
      top: edgeOffset,
      left: 0.0,
      bottom: edgeOffset,
      right: 0.0
    )
  }
}
Jesse Crossen
la source
5
Merveilleux - merci! Je pense que beaucoup d'autres réponses vont à ce sujet "à la dure" - cela semble beaucoup mieux.
Joe D'Andrea
3
J'ai trouvé ce qui précède au wok, mais je n'ai absolument aucun modèle mental pour savoir comment. Quelqu'un a-t-il un lien vers une explication illustrée de ce que les paramètres individuels EdgeInsets affectent? Et pourquoi la largeur du texte aurait-elle changé?
Robert Atkins
3
Cela ne fonctionne pas lorsque l'image est réduite pour s'adapter au bouton. Il semble qu'UIButton (au moins dans iOS 7) utilise l'image.size, pas l'imageView.frame.size pour ses calculs de centrage.
Dan Jackson
5
@Hemang et Dan Jackson, j'incorpore vos suggestions sans les tester moi-même. Je trouve un peu ridicule que j'aie initialement écrit ceci pour iOS 4, et après autant de versions, nous devons encore procéder à une ingénierie inverse de l'algorithme de mise en page d'Apple pour obtenir une fonctionnalité aussi évidente. Ou du moins, je suppose qu'il n'y a pas encore de meilleure solution à partir du flux cohérent de votes positifs et des réponses tout aussi hackish ci-dessous (aucune insulte n'est prévue).
Jesse Crossen
5
Dans iOS8, j'ai trouvé qu'il était préférable d'utiliser button.currentTitleau lieu de button.titleLabel.textsurtout si le texte du bouton changera un jour. currentTitleest titleLabel.textrempli immédiatement, alors que le changement peut être lent, ce qui peut conduire à des inserts mal alignés.
mjangda
59

Trouvé comment.

Tout d'abord, configurez le texte de titleLabel(à cause des styles, c'est-à-dire gras, italique, etc.). Ensuite, utilisez en setTitleEdgeInsetstenant compte de la largeur de votre image:

[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button setTitle:title forState:UIControlStateNormal];
[button.titleLabel setFont:[UIFont boldSystemFontOfSize:10.0]];

// Left inset is the negative of image width.
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, -image.size.width, -25.0, 0.0)]; 

Après cela, utilisez en setTitleEdgeInsetstenant compte de la largeur des limites du texte:

[button setImage:image forState:UIControlStateNormal];

// Right inset is the negative of text bounds width.
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, -button.titleLabel.bounds.size.width)];

Maintenant, l'image et le texte seront centrés (dans cet exemple, l'image apparaît au-dessus du texte).

À votre santé.

Reinaldoluckman
la source
3
7 ans, mec. Je ne m'en souviens vraiment pas. Quand j'ai demandé, je me suis répondu (ma réponse était la seule à l'époque). J'ai changé la réponse sélectionnée lorsque l'auteur de la réponse sélectionnée actuelle s'est concentré sur l'élimination de ces nombres magiques. Ainsi, dans la réponse sélectionnée, vous pouvez comprendre ce qu'ils signifient.
reinaldoluckman
20

Vous pouvez le faire avec cette extension Swift, qui était en partie basée sur la réponse de Jesse Crossen:

extension UIButton {
  func centerLabelVerticallyWithPadding(spacing:CGFloat) {
    // update positioning of image and title
    let imageSize = self.imageView.frame.size
    self.titleEdgeInsets = UIEdgeInsets(top:0,
                                        left:-imageSize.width,
                                        bottom:-(imageSize.height + spacing),
                                        right:0)
    let titleSize = self.titleLabel.frame.size
    self.imageEdgeInsets = UIEdgeInsets(top:-(titleSize.height + spacing),
                                        left:0,
                                        bottom: 0,
                                        right:-titleSize.width)

    // reset contentInset, so intrinsicContentSize() is still accurate
    let trueContentSize = CGRectUnion(self.titleLabel.frame, self.imageView.frame).size
    let oldContentSize = self.intrinsicContentSize()
    let heightDelta = trueContentSize.height - oldContentSize.height
    let widthDelta = trueContentSize.width - oldContentSize.width
    self.contentEdgeInsets = UIEdgeInsets(top:heightDelta/2.0,
                                          left:widthDelta/2.0,
                                          bottom:heightDelta/2.0,
                                          right:widthDelta/2.0)
  }
}

Cela définit une fonction centerLabelVerticallyWithPaddingqui définit le titre et les incrustations d'image de manière appropriée.

Il définit également contentEdgeInsets, ce qui, à mon avis, est nécessaire pour garantir qu'il intrinsicContentSizefonctionne toujours correctement, ce qui devrait utiliser la mise en page automatique.

Je crois que toutes les solutions qui sous-classent UIButton sont techniquement illégitimes, puisque vous n'êtes pas censé sous-classer les contrôles UIKit. Ie, en théorie, ils pourraient casser dans les versions futures.

algue
la source
Test sur iOS9. L'image apparaît centrée, mais le texte apparaît à gauche :(
endavid
13

Edit: mis à jour pour Swift 3

Si vous recherchez une solution Swift de la réponse de Jesse Crossen, vous pouvez l'ajouter à une sous-classe de UIButton:

override func layoutSubviews() {

    let spacing: CGFloat = 6.0

    // lower the text and push it left so it appears centered
    //  below the image
    var titleEdgeInsets = UIEdgeInsets.zero
    if let image = self.imageView?.image {
        titleEdgeInsets.left = -image.size.width
        titleEdgeInsets.bottom = -(image.size.height + spacing)
    }
    self.titleEdgeInsets = titleEdgeInsets

    // raise the image and push it right so it appears centered
    //  above the text
    var imageEdgeInsets = UIEdgeInsets.zero
    if let text = self.titleLabel?.text, let font = self.titleLabel?.font {
        let attributes = [NSFontAttributeName: font]
        let titleSize = text.size(attributes: attributes)
        imageEdgeInsets.top = -(titleSize.height + spacing)
        imageEdgeInsets.right = -titleSize.width
    }
    self.imageEdgeInsets = imageEdgeInsets

    super.layoutSubviews()
}
Thomas Verbeek
la source
9

Il y a de bons exemples ici, mais je ne pouvais pas faire fonctionner cela dans tous les cas lorsque je traitais également plusieurs lignes de texte (habillage de texte). Pour enfin le faire fonctionner, j'ai combiné quelques techniques:

  1. J'ai utilisé l'exemple de Jesse Crossen ci-dessus. Cependant, j'ai corrigé un problème de hauteur du texte et j'ai ajouté la possibilité de spécifier une marge de texte horizontale. La marge est utile lorsque vous autorisez le texte à s'habiller afin qu'il ne touche pas le bord du bouton:

    // the space between the image and text
    CGFloat spacing = 10.0;
    float   textMargin = 6;
    
    // get the size of the elements here for readability
    CGSize  imageSize   = picImage.size;
    CGSize  titleSize   = button.titleLabel.frame.size;
    CGFloat totalHeight = (imageSize.height + titleSize.height + spacing);      // get the height they will take up as a unit
    
    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake( 0.0, -imageSize.width +textMargin, - (totalHeight - titleSize.height), +textMargin );   // top, left, bottom, right
    
    // the text width might have changed (in case it was shortened before due to 
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.bounds.size;
    
    button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width );     // top, left, bottom, right        
  2. Assurez-vous que vous avez configuré l'étiquette de texte pour envelopper

    button.titleLabel.numberOfLines = 2; 
    button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
    button.titleLabel.textAlignment = UITextAlignmentCenter;
  3. Cela fonctionnera principalement maintenant. Cependant, j'avais des boutons qui ne rendaient pas correctement leur image. L'image a été déplacée vers la droite ou vers la gauche (elle n'était pas centrée). J'ai donc utilisé une technique de remplacement de disposition UIButton pour forcer l'imageView à être centrée.

    @interface CategoryButton : UIButton
    @end
    
    @implementation CategoryButton
    
    - (void)layoutSubviews
    {
        // Allow default layout, then center imageView
        [super layoutSubviews];
    
        UIImageView *imageView = [self imageView];
        CGRect imageFrame = imageView.frame;
        imageFrame.origin.x = (int)((self.frame.size.width - imageFrame.size.width)/ 2);
        imageView.frame = imageFrame;
    }
    @end
Tod Cunningham
la source
On dirait que c'est une bonne solution, mais button.titleLabel.numberOfLines ne devrait-il pas être égal à 0 pour qu'il puisse contenir autant de lignes qu'il le souhaite?
Ben Lachman
Dans mon cas, je ne voulais que deux lignes. Sinon, l'image aurait des problèmes avec la taille globale du bouton.
Tod Cunningham
9

J'ai créé une méthode pour la réponse de @ TodCunningham

 -(void) AlignTextAndImageOfButton:(UIButton *)button
 {
   CGFloat spacing = 2; // the amount of spacing to appear between image and title
   button.imageView.backgroundColor=[UIColor clearColor];
   button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
   button.titleLabel.textAlignment = UITextAlignmentCenter;
   // get the size of the elements here for readability
   CGSize imageSize = button.imageView.frame.size;
   CGSize titleSize = button.titleLabel.frame.size;

  // lower the text and push it left to center it
  button.titleEdgeInsets = UIEdgeInsetsMake(0.0, - imageSize.width, - (imageSize.height   + spacing), 0.0);

  // the text width might have changed (in case it was shortened before due to 
  // lack of space and isn't anymore now), so we get the frame size again
   titleSize = button.titleLabel.frame.size;

  // raise the image and push it right to center it
  button.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, -     titleSize.width);
 }
Bekir Onat Akin
la source
7

Mis à jour pour Xcode 11+

Les inserts décrits dans ma réponse originale ont été déplacés vers l'inspecteur de taille dans les versions plus récentes de Xcode. Je ne suis pas sûr à 100% du moment où le changement s'est produit, mais les lecteurs devraient consulter l'inspecteur de taille si les informations en médaillon ne sont pas trouvées dans l'inspecteur d'attributs. Vous trouverez ci-dessous un exemple du nouvel écran encart (situé en haut de l'inspecteur des attributs de taille à partir de 11.5).

insets_moved_to_size_inspector

Réponse originale

Rien de mal avec les autres réponses, mais je voulais juste noter que le même comportement peut être accompli visuellement dans Xcode en utilisant zéro ligne de code. Cette solution est utile si vous n'avez pas besoin d'une valeur calculée ou si vous construisez avec un storyboard / xib (sinon d'autres solutions s'appliquent).

Remarque - Je comprends que la question du PO nécessite un code. Je ne fais que fournir cette réponse par souci d'exhaustivité et comme alternative logique pour ceux qui utilisent des storyboards / xibs.

Pour modifier l'espacement des vues d'image, de titre et de contenu d'un bouton à l'aide d'inserts de bord, vous pouvez sélectionner le bouton / contrôle et ouvrir l'inspecteur d'attributs. Faites défiler vers le milieu de l'inspecteur et recherchez la section des inserts Edge.

encarts de bord

On peut également accéder et modifier les inserts de bord spécifiques pour la vue titre, image ou contenu.

Options de menu

Tommie C.
la source
Ce que je ne comprends pas, c'est pourquoi je n'arrive pas à saisir des nombres négatifs dans le storyboard pour certaines valeurs.
Daniel T.28
Est-ce que cela fonctionne pour la nouvelle version Swift? Je ne trouve nulle part l'attribut Edge
brockhampton
@brockhampton - voir la réponse mise à jour pour le nouvel emplacement
Tommie C.
6

Ne combattez pas le système. Si vos mises en page deviennent trop complexes à gérer avec Interface Builder + peut-être un code de configuration simple, effectuez les mises en page manuellement de manière plus simple en utilisantlayoutSubviews - c'est à ça que ça sert! Tout le reste équivaudra à des hacks.

Créez une sous-classe UIButton et remplacez sa layoutSubviewsméthode pour aligner votre texte et image par programmation. Ou utilisez quelque chose comme https://github.com/nickpaulson/BlockKit/blob/master/Source/UIView-BKAdditions.h afin de pouvoir implémenter layoutSubviews à l'aide d'un bloc.

Steven Kramer
la source
6

Sous-classe UIButton

- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat spacing = 6.0;
    CGSize imageSize = self.imageView.image.size;
    CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(self.frame.size.width, self.frame.size.height - (imageSize.height + spacing))];
    self.imageView.frame = CGRectMake((self.frame.size.width - imageSize.width)/2, (self.frame.size.height - (imageSize.height+spacing+titleSize.height))/2, imageSize.width, imageSize.height);
    self.titleLabel.frame = CGRectMake((self.frame.size.width - titleSize.width)/2, CGRectGetMaxY(self.imageView.frame)+spacing, titleSize.width, titleSize.height);
}
Alex
la source
6

Réponse mise à jour de Jesse Crossen pour Swift 4 :

extension UIButton {
    func alignVertical(spacing: CGFloat = 6.0) {
        guard let imageSize = self.imageView?.image?.size,
            let text = self.titleLabel?.text,
            let font = self.titleLabel?.font
            else { return }
        self.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0.0)
        let labelString = NSString(string: text)
        let titleSize = labelString.size(withAttributes: [kCTFontAttributeName as NSAttributedStringKey: font])
        self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsets(top: edgeOffset, left: 0.0, bottom: edgeOffset, right: 0.0)
    }
}

Utilisez de cette façon:

override func viewDidLayoutSubviews() {
    button.alignVertical()
}
Doci
la source
Après de nombreuses heures. C'était la seule chose qui fonctionnait :)
Harry Blue
4

Avec ce morceau de code, vous obtiendrez quelque chose comme ça alignement du titre et de l'image

extension UIButton {
    func alignTextUnderImage() {
        guard let imageView = imageView else {
                return
        }
        self.contentVerticalAlignment = .Top
        self.contentHorizontalAlignment = .Center
        let imageLeftOffset = (CGRectGetWidth(self.bounds) - CGRectGetWidth(imageView.bounds)) / 2//put image in center
        let titleTopOffset = CGRectGetHeight(imageView.bounds) + 5
        self.imageEdgeInsets = UIEdgeInsetsMake(0, imageLeftOffset, 0, 0)
        self.titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset, -CGRectGetWidth(imageView.bounds), 0, 0)
    }
}
Bohdan Savych
la source
2
J'ai écrit une petite extension pour positionner l'image et le texte à l'intérieur du bouton. Si vous êtes intéressé, voici le code source. github.com/sssbohdan/ButtonAlignmentExtension/blob/master/…
Bohdan Savych
4

Extension UIButton avec syntaxe Swift 3+ :

extension UIButton {
    func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
        let imageSize: CGSize = imageView!.image!.size
        titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + padding), 0.0)
        let labelString = NSString(string: titleLabel!.text!)
        let titleSize = labelString.size(attributes: [NSFontAttributeName: titleLabel!.font])
        self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + padding), 0.0, 0.0, -titleSize.width)
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
    }
}

Réponse originale: https://stackoverflow.com/a/7199529/3659227

Maverick
la source
3

Juste un changement mineur à la réponse de Jesse Crossen qui l'a fait fonctionner parfaitement pour moi:

au lieu de:

CGSize titleSize = button.titleLabel.frame.size;

J'ai utilisé ceci:

CGSize titleSize = [button.titleLabel.text sizeWithAttributes: @{NSFontAttributeName:button.titleLabel.font}];
César
la source
Bienvenue à SO! Au lieu d'ajouter une réponse distincte (pour un changement mineur), vous pouvez l'écrire directement à Jesse afin qu'il puisse vérifier et mettre à jour correctement sa réponse [acceptée] (si nécessaire).
Hemang
3

L'utilisation ne button.titleLabel.frame.size.widthfonctionne bien que tant que l'étiquette est suffisamment courte pour ne pas être tronquée. Cependant, lorsque le texte de l'étiquette est tronqué, le positionnement ne fonctionne pas. Prise

CGSize titleSize = [[[button titleLabel] text] sizeWithFont:[[button titleLabel] font]];

fonctionne pour moi même lorsque le texte de l'étiquette est tronqué.

décembre
la source
vous avez une faute de frappe.
Bhimbim
2

J'ai regardé les réponses existantes, mais j'ai également constaté que le réglage du cadre du bouton est une première étape importante.

Voici une fonction que j'utilise qui s'occupe de ceci:

const CGFloat kImageTopOffset   = -15;
const CGFloat kTextBottomOffset = -25;

+ (void) centerButtonImageTopAndTextBottom: (UIButton*)         button 
                                     frame: (CGRect)            buttonFrame
                                      text: (NSString*)         textString
                                 textColor: (UIColor*)          textColor
                                      font: (UIFont*)           textFont
                                     image: (UIImage*)          image
                                  forState: (UIControlState)    buttonState
{
    button.frame = buttonFrame;

    [button setTitleColor: (UIColor*)       textColor
                 forState: (UIControlState) buttonState];

    [button setTitle: (NSString*) textString
            forState: (UIControlState) buttonState ];


    [button.titleLabel setFont: (UIFont*) textFont ];

    [button setTitleEdgeInsets: UIEdgeInsetsMake( 0.0, -image.size.width, kTextBottomOffset,  0.0)]; 

    [button setImage: (UIImage*)       image 
            forState: (UIControlState) buttonState ];

    [button setImageEdgeInsets: UIEdgeInsetsMake( kImageTopOffset, 0.0, 0.0,- button.titleLabel.bounds.size.width)];
}
Bamaco
la source
2

Ou vous pouvez simplement utiliser cette catégorie:

@interface UIButton (VerticalLayout)  

- (void)centerVerticallyWithPadding:(float)padding;  
- (void)centerVertically;  

@end  


@implementation UIButton (VerticalLayout)  

- (void)centerVerticallyWithPadding:(float)padding 
{      
    CGSize imageSize = self.imageView.frame.size;  
    CGSize titleSize = self.titleLabel.frame.size;  

    CGFloat totalHeight = (imageSize.height + titleSize.height + padding);  

    self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
                                            0.0f,
                                            0.0f,
                                            - titleSize.width);

    self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
                                            - imageSize.width,
                                            - (totalHeight - titleSize.height),
                                            0.0f);

}


- (void)centerVertically
{  
    const CGFloat kDefaultPadding = 6.0f;

    [self centerVerticallyWithPadding:kDefaultPadding];  
}  


@end
RaffAl
la source
1
Cela ne fonctionnerait pas avec une police personnalisée. À partir d'iOS7 +,CGSize titleSize = [button.titleLabel.text sizeWithAttributes: @{NSFontAttributeName: button.titleLabel.font}];
Hemang
1

Mon cas d'utilisation a rendu les inserts ingérables:

  1. l'image d'arrière-plan sur le bouton reste cohérente
  2. le texte et l'image dynamiques changent lorsque la longueur de la chaîne et la taille de l'image varient

C'est ce que j'ai fini par faire et j'en suis plutôt content:

  • Créez le bouton sur le storyboard avec une image d'arrière-plan (cercle rond avec flou et couleur).

  • Déclarez un UIImageView dans ma classe:

    @implementation BlahViewController {
        UIImageView *_imageView;
    }
  • Créez une instance de vue d'image sur init:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            _imageView = [[UIImageView alloc] initWithCoder:aDecoder];
         }
         return self;
     }
  • Dans viewDidLoad, ajoutez un nouveau calque au bouton de notre vue d'image et définissez l'alignement du texte:

    [self.btn addSubview:_imageView];
    [self.btn.titleLabel setTextAlignment:NSTextAlignmentCenter];
  • Dans la méthode du clic sur le bouton, ajoutez l'image de superposition choisie à la vue de l'image, redimensionnez-la pour l'adapter à l'image et centrez-la dans le bouton, mais déplacez-la vers le haut de 15 afin que je puisse mettre le décalage de texte en dessous:

    [_imageView setImage:[UIImage imageNamed:@"blahImageBlah]];
    [_imageView sizeToFit];
    _imageView.center = CGPointMake(ceilf(self.btn.bounds.size.width / 2.0f),
             ceilf((self.btn.bounds.size.height / 2.0f) - 15));
    [self.btn setTitle:@"Some new text" forState:UIControlStateNormal];

Remarque: ceilf () est important pour s'assurer qu'il est sur une limite de pixels pour la qualité d'image.

Oliver Dungey
la source
1
Certainement une meilleure approche pour mon cas d'utilisation puisque j'ajoute le bouton à une vue de pile.
valeCocoa
0

En supposant que vous souhaitiez que le texte et l'image soient centrés horizontalement, image au-dessus du texte: Centrez le texte du générateur d'interface et ajoutez un encart supérieur (faisant de la place pour l'image). (laissez l'encart gauche à 0). Utilisez le générateur d'interface pour choisir l'image - sa position réelle sera définie à partir du code, alors ne vous inquiétez pas que les choses ne vont pas bien dans IB. Contrairement aux autres réponses ci-dessus, cela fonctionne sur toutes les versions iOS actuellement prises en charge (5, 6 et 7).

Dans le code, supprimez simplement le ImageView du bouton (en définissant l'image du bouton sur null) après avoir saisi l'image (cela centrera également automatiquement le texte, enveloppé si nécessaire). Ensuite, instanciez votre propre ImageView avec la même taille de cadre et la même image et positionnez-la au milieu.

De cette façon, vous pouvez toujours choisir l'image dans le générateur d'interface (bien qu'elle ne soit pas alignée dans IB comme dans le simulateur, mais là encore, les autres solutions ne sont pas compatibles avec toutes les versions ios prises en charge)

Radu Simionescu
la source
0

J'avais du mal à faire cela car je ne pouvais pas obtenir la taille de l'image et la largeur du texte sur le constructeur de ma vue. Deux changements mineurs sur la réponse de Jesse ont fonctionné pour moi:

CGFloat spacing = 3;
self.titleEdgeInsets = UIEdgeInsetsMake(0.0, - image.size.width, - (image.size.height + spacing), 0.0);
CGSize titleSize = [name sizeWithAttributes:@{NSFontAttributeName:self.titleLabel.font}];
self.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

Les changements sont:

  • En utilisant [NSString sizeWithAttributes] pour obtenir la largeur du texte;
  • Obtenez la taille de l'image directement sur le UIImagelieu deUIImageView
saulobrito
la source
0

Cela fonctionne bien pour moi, pour plusieurs boutons, avec une largeur d'image différente et une longueur de titre différente:

Sous-classe UIButton

override func layoutSubviews() {
    super.layoutSubviews()

    if let image = imageView?.image {

        let margin = 30 - image.size.width / 2
        let titleRect = titleRectForContentRect(bounds)
        let titleOffset = (bounds.width - titleRect.width - image.size.width - margin) / 2


        contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
            imageEdgeInsets = UIEdgeInsetsMake(0, margin, 0, 0)
            titleEdgeInsets = UIEdgeInsetsMake(0, (bounds.width - titleRect.width -  image.size.width - margin) / 2, 0, 0)
    }

}
Rémy Virin
la source
0

Fonctionne bien pour la taille du bouton 80x80 pixels.

[self.leftButton setImageEdgeInsets:UIEdgeInsetsMake(0, 10.0, 20.0, 10.0)];    
[self.leftButton setTitleEdgeInsets:UIEdgeInsetsMake(60, -75.0, 0.0, 0.0)];
BADRI
la source
0

J'ai fait quelques ajustements pour que l'image soit alignée au centre horizontal:

// the space between the image and text
        let spacing = CGFloat(36.0);

        // lower the text and push it left so it appears centered
        //  below the image
        let imageSize = tutorialButton.imageView!.frame.size;
        tutorialButton.titleEdgeInsets = UIEdgeInsetsMake(
            0, -CGFloat(imageSize.width), -CGFloat(imageSize.height + spacing), 0.0);

        // raise the image and push it right so it appears centered
        //  above the text
        let titleSize = tutorialButton.titleLabel!.frame.size;
        tutorialButton.imageEdgeInsets = UIEdgeInsetsMake(
            -CGFloat(titleSize.height + spacing), CGFloat((tutorialButton.frame.width - imageSize.width) / 2), 0.0, -CGFloat(titleSize.width));
Nguyễn Thanh Khiêm
la source
0

est-il obligatoire d'utiliser des inserts de bord? Sinon, vous pouvez essayer de positionner par rapport à la vue parent centrale

extension UIButton 
{
    func centerImageAndTextVerticaAlignment(spacing: CGFloat) 
    {
        var titlePoint : CGPoint = convertPoint(center, fromView:superview)
        var imageViewPoint : CGPoint = convertPoint(center, fromView:superview)
        titlePoint.y += ((titleLabel?.size.height)! + spacing)/2
        imageViewPoint.y -= ((imageView?.size.height)! + spacing)/2
        titleLabel?.center = titlePoint
        imageView?.center = imageViewPoint

    }
}
Rodrigo Birriel
la source
La question demande explicitement l'utilisation de imageEdgeInsets et titleEdgeInsets, il est donc probable que ce soit obligatoire
Tibrogargan
0

Vous devez déplacer l'image vers la droite de la largeur du texte. Déplacez ensuite le texte vers la gauche de la largeur de l'image.

UIEdgeInsets imageEdgeInsets = self.remoteCommandsButtonLights.imageEdgeInsets;
imageEdgeInsets.left = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName:[button.titleLabel font]}].width;
imageEdgeInsets.bottom = 14.0;
button.imageEdgeInsets = imageEdgeInsets;

UIEdgeInsets titleEdgeInsets = self.remoteCommandsButtonLights.titleEdgeInsets;
titleEdgeInsets.left = -button.currentImage.size.width;
titleEdgeInsets.top = 20.0;
button.titleEdgeInsets = titleEdgeInsets;

Ajustez ensuite les inserts supérieur et inférieur pour ajuster l'axe Y. Cela peut également être fait par programme, mais doit être constant pour la taille de votre image. Alors que les inserts de l'axe X devront changer en fonction de la taille de l'étiquette de texte dans chaque bouton.

pkamb
la source
0

Ajoutez ce code dans l'extension Swift 4.2

 func moveImageLeftTextCenter(imagePadding: CGFloat = 30.0){
    guard let imageViewWidth = self.imageView?.frame.width else{return}
    guard let titleLabelWidth = self.titleLabel?.intrinsicContentSize.width else{return}
    self.contentHorizontalAlignment = .left
    imageEdgeInsets = UIEdgeInsets(top: 0.0, left: imagePadding - imageViewWidth / 2, bottom: 0.0, right: 0.0)
    titleEdgeInsets = UIEdgeInsets(top: 0.0, left: (bounds.width - titleLabelWidth) / 2 - imageViewWidth, bottom: 0.0, right: 0.0)
}
func moveImageRIghtTextCenter(imagePadding: CGFloat = 30.0){
    guard let imageViewWidth = self.imageView?.frame.width else{return}
    guard let titleLabelWidth = self.titleLabel?.intrinsicContentSize.width else{return}
    self.contentHorizontalAlignment = .right
    imageEdgeInsets = UIEdgeInsets(top: 0.0, left:0.0 , bottom: 0.0, right: imagePadding - imageViewWidth / 2)
    titleEdgeInsets = UIEdgeInsets(top: 0.0, left:0.0 , bottom: 0.0, right:(bounds.width - titleLabelWidth) / 2 - imageViewWidth)
}
Shahzaib Maqbool
la source
0

Juste pour jeter mes 2 cents, cela a fonctionné pour moi:

extension UIButton {
  public func centerImageAndTextVertically(spacing: CGFloat) {
    layoutIfNeeded()
    let contentFrame = contentRect(forBounds: bounds)
    let imageFrame = imageRect(forContentRect: contentFrame)
    let imageLeftInset = bounds.size.width * 0.5 - imageFrame.size.width * 0.5
    let imageTopInset = -(imageFrame.size.height + spacing * 0.5)
    let titleFrame = titleRect(forContentRect: contentFrame)
    let titleLeftInset = ((bounds.size.width - titleFrame.size.width) * 0.5) - imageFrame.size.width
    let titleTopInmset = titleFrame.size.height + spacing * 0.5
    imageEdgeInsets = UIEdgeInsets(top: imageTopInset, left: imageLeftInset, bottom: 0, right: 0)
    titleEdgeInsets = UIEdgeInsets(top: titleTopInmset, left: titleLeftInset, bottom: 0, right: 0)
  }
}
Chris Wooden
la source