Alignement du texte et de l'image sur UIButton avec imageEdgeInsets et titleEdgeInsets

249

Je voudrais placer une icône à gauche des deux lignes de texte de sorte qu'il y ait environ 2-3 pixels d'espace entre l'image et le début du texte. Le contrôle lui-même est aligné au centre horizontalement (défini via Interface Builder)

Le bouton ressemblerait à quelque chose comme ceci:

|                  |
|[Image] Add To    |
|        Favorites |

J'essaie de configurer cela avec contentEdgeInset, imageEdgeInsets et titleEdgeInsets en vain. Je comprends qu'une valeur négative élargit le bord tandis qu'une valeur positive le rétrécit pour le rapprocher du centre.

J'ai essayé:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];

mais cela ne l'affiche pas correctement. J'ai modifié les valeurs, mais passer de -5 à -10 sur la valeur de l'encart de gauche ne semble pas la déplacer de la manière attendue. -10 va parcourir le texte complètement à gauche, donc je m'attendais à ce que -5 le fasse à mi-chemin du côté gauche, mais ce n'est pas le cas.

Quelle est la logique derrière les encarts? Je ne connais pas les emplacements d'image et la terminologie associée.

J'ai utilisé cette question SO comme référence, mais quelque chose à propos de mes valeurs n'est pas correct. UIButton: comment centrer une image et un texte en utilisant imageEdgeInsets et titleEdgeInsets?

Justin Galzic
la source

Réponses:

392

J'accepte la documentation imageEdgeInsetsettitleEdgeInsets devrait être meilleure, mais j'ai compris comment obtenir le positionnement correct sans recourir aux essais et erreurs.

L'idée générale est ici à cette question , mais c'était si vous vouliez que le texte et l'image soient centrés. Nous ne voulons pas que l'image et le texte soient centrés individuellement, nous voulons que l'image et le texte soient centrés ensemble comme une seule entité. C'est en fait ce que fait déjà UIButton, il nous suffit donc d'ajuster l'espacement.

CGFloat spacing = 10; // the amount of spacing to appear between image and title
tabBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);

J'ai également transformé cela en une catégorie pour UIButton afin qu'il soit facile à utiliser:

UIButton + Position.h

@interface UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;

@end

UIButton + Position.m

@implementation UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing {
    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
}

@end

Alors maintenant, tout ce que j'ai à faire est de:

[button centerButtonAndImageWithSpacing:10];

Et j'obtiens ce dont j'ai besoin à chaque fois. Plus besoin de jouer avec les inserts de bord manuellement.

EDIT: échange d'image et de texte

En réponse à @Javal dans les commentaires

En utilisant ce même mécanisme, nous pouvons échanger l'image et le texte. Pour effectuer l'échange, utilisez simplement un espacement négatif mais incluez également la largeur du texte et de l'image. Cela nécessitera que les cadres soient connus et que la mise en page soit déjà effectuée.

[self.view layoutIfNeeded];
CGFloat flippedSpacing = -(desiredSpacing + button.currentImage.size.width + button.titleLabel.frame.size.width);
[button centerButtonAndImageWithSpacing:flippedSpacing];

Bien sûr, vous voudrez probablement faire une bonne méthode pour cela, en ajoutant éventuellement une méthode de deuxième catégorie, cela est laissé comme exercice au lecteur.

Kekoa
la source
Si j'ai des titres différents pour normal et surligné, comment puis-je le recentrer lorsque l'utilisateur met en surbrillance et dé-surligne le bouton?
user102008
@ user102008 Des titres complètement différents? Ou juste des couleurs différentes? Le positionnement ne doit pas changer si vous utilisez [UIButton setTitleColor:forState:], ni même [UIButton setTitle:forState:].
Kekoa
5
Comment fixez-vous [bouton sizeToFit] pour qu'il taille correctement?
RonLugge
@RonLugge Je ne sais pas pourquoi vous avez besoin de sizeToFit, donc je ne peux pas répondre à votre question. Cela fonctionne bien pour moi sans utiliser sizeToFit.
Kekoa
J'ai besoin de sizeToFit car les boutons / texte sont dynamiques, j'ai donc besoin de dimensionner le bouton pour l'adapter à l'étiquette (définie par l'utilisateur). Le problème est qu'il ne compense pas l'espace ajouté. J'ai fini par le remplacer et en augmentant manuellement la largeur du cadre de 10.
RonLugge
397

Je suis un peu en retard à cette fête, mais je pense que j'ai quelque chose d'utile à ajouter.

La réponse de Kekoa est excellente mais, comme le mentionne RonLugge, cela peut rendre le bouton plus respectueux sizeToFitou, plus important encore, provoquer le bouton pour couper son contenu lorsqu'il est intrinsèquement dimensionné. Oui!

Mais d'abord,

Une brève explication de la façon dont je crois imageEdgeInsetset titleEdgeInsetstravaille:

Les documents pourimageEdgeInsets ont ce qui suit à dire, en partie:

Utilisez cette propriété pour redimensionner et repositionner le rectangle de dessin efficace pour l'image du bouton. Vous pouvez spécifier une valeur différente pour chacun des quatre encarts (haut, gauche, bas, droite). Une valeur positive rétrécit ou insère ce bord, en le rapprochant du centre du bouton. Une valeur négative étend ou dépasse ce bord.

Je crois que cette documentation a été écrite en imaginant que le bouton n'a pas de titre, juste une image. Il est beaucoup plus logique de penser de cette façon et se comporte comme d' UIEdgeInsetshabitude. Fondamentalement, le cadre de l'image (ou le titre, avec titleEdgeInsets) est déplacé vers l'intérieur pour les encarts positifs et vers l'extérieur pour les encarts négatifs.

Okay, alors quoi?

J'y arrive! Voici ce que vous avez par défaut, en définissant une image et un titre (la bordure du bouton est verte juste pour montrer où elle est):

Image de départ;  pas d'espace entre le titre et l'image.

Lorsque vous souhaitez un espacement entre une image et un titre, sans provoquer l’écrasement de l’un ou l’autre, vous devez définir quatre encarts différents, deux sur l’image et le titre. C'est parce que vous ne voulez pas changer la taille des cadres de ces éléments, mais simplement leur position. Lorsque vous commencez à penser de cette façon, le changement nécessaire à l'excellente catégorie de Kekoa devient clair:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
}

@end

Mais attendez , vous dites, quand je fais ça, je reçois ceci:

L'espacement est bon, mais l'image et le titre sont en dehors du cadre de la vue.

Oh oui! J'ai oublié, les docs m'ont prévenu à ce sujet. Ils disent, en partie:

Cette propriété est utilisée uniquement pour positionner l'image pendant la mise en page. Le bouton n'utilise pas cette propriété pour déterminer intrinsicContentSizeet sizeThatFits:.

Mais il y a une propriété qui peut aider, et c'est contentEdgeInsets. Les documents pour cela disent, en partie:

Le bouton utilise cette propriété pour déterminer intrinsicContentSizeet sizeThatFits:.

Ça sonne bien. Alors, ajustons la catégorie une fois de plus:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
    self.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
}

@end

Et qu'obtenez-vous?

L'espacement et le cadre sont maintenant corrects.

On dirait un gagnant pour moi.


Vous travaillez chez Swift et vous ne voulez pas réfléchir du tout? Voici la version finale de l'extension dans Swift:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
        contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}
ravron
la source
25
Quelle excellente réponse! Oui, quelques années de retard pour la fête, mais cela a résolu le problème de l' intrinsicContentSizeerreur, ce qui est très important en ces jours de mise en page automatique depuis que la réponse originale a été acceptée.
Acey
2
Et si vous vouliez la même quantité d'espacement entre l'extérieur du bouton et l'image et l'étiquette, ajoutez spacingà chacune des quatre valeurs de self.contentEdgeInsets, comme ceci:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut
1
Excellente réponse, dommage que cela ne fonctionne pas très bien lorsque l'image est alignée à droite et que la longueur du texte peut varier.
jlpiedrahita
2
Réponse presque parfaite! La seule chose qui manque, c'est que les encarts de l'image et du titre doivent être inversés lorsqu'il s'exécute sur l'interface de droite à gauche.
jeeeyul
comment définir le rapport d'image à 1 pour 1
Yestay Muratov
39

Dans l'interface Builder. Sélectionnez le bouton UIButton -> Inspector Attributes -> Edge = Title et modifiez les encarts de bord

Homme libre
la source
38

Aussi, si vous voulez faire quelque chose de similaire à

entrez la description de l'image ici

Vous avez besoin

1.Définissez l'alignement horizontal et vertical pour que le bouton

entrez la description de l'image ici

  1. Trouver toutes les valeurs requises et définir UIImageEdgeInsets

            CGSize buttonSize = button.frame.size;
            NSString *buttonTitle = button.titleLabel.text;
            CGSize titleSize = [buttonTitle sizeWithAttributes:@{ NSFontAttributeName : [UIFont camFontZonaProBoldWithSize:12.f] }];
            UIImage *buttonImage = button.imageView.image;
            CGSize buttonImageSize = buttonImage.size;
    
            CGFloat offsetBetweenImageAndText = 10; //vertical space between image and text
    
            [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText,
                                                        (buttonSize.width - buttonImageSize.width) / 2,
                                                        0,0)];                
            [button setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText,
                                                        titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.width  +  (buttonSize.width - titleSize.width) / 2 : (buttonSize.width - titleSize.width) / 2 - buttonImage.size.width,
                                                        0,0)];

Cela organisera votre titre et votre image sur le bouton.

Veuillez également noter la mise à jour sur chaque relais


Rapide

import UIKit

extension UIButton {
    // MARK: - UIButton+Aligment

    func alignContentVerticallyByCenter(offset:CGFloat = 10) {
        let buttonSize = frame.size

        if let titleLabel = titleLabel,
            let imageView = imageView {

            if let buttonTitle = titleLabel.text,
                let image = imageView.image {
                let titleString:NSString = NSString(string: buttonTitle)
                let titleSize = titleString.sizeWithAttributes([
                    NSFontAttributeName : titleLabel.font
                    ])
                let buttonImageSize = image.size

                let topImageOffset = (buttonSize.height - (titleSize.height + buttonImageSize.height + offset)) / 2
                let leftImageOffset = (buttonSize.width - buttonImageSize.width) / 2
                imageEdgeInsets = UIEdgeInsetsMake(topImageOffset,
                                                   leftImageOffset,
                                                   0,0)

                let titleTopOffset = topImageOffset + offset + buttonImageSize.height
                let leftTitleOffset = (buttonSize.width - titleSize.width) / 2 - image.size.width

                titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset,
                                                   leftTitleOffset,
                                                   0,0)
            }
        }
    }
}
gbk
la source
29

Vous pouvez éviter beaucoup de problèmes en utilisant ceci -

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

Cela alignera automatiquement tout votre contenu à gauche (ou où vous le souhaitez)

Swift 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Nishant
la source
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; // Typo corrigée
Naishta
25

Dans Xcode 8.0, vous pouvez simplement le faire en modifiantinsets inspecteur de taille.

Sélectionnez le bouton UIB -> Inspecteur d'attributs -> allez à l'inspecteur de taille et modifiez le contenu, l'image et les encarts de titre.

entrez la description de l'image ici

Et si vous voulez changer l'image sur le côté droit, vous pouvez simplement changer la propriété sémantique Force Right-to-leftdans l'inspecteur d'attributs.

entrez la description de l'image ici

Sahil
la source
dans mon cas, l'image du bouton ne se déplace jamais à droite du texte avec xcode 10? pouvez-vous aider?
Satish Mavani
Salut Satish, cela fonctionne également très bien avec xcode 10. J'espère que vous définissez l'image et non l'image d'arrière-plan et que vous pouvez également modifier les insectes de l'image à l'aide de l'inspecteur de taille.
Sahil
18

Je suis un peu en retard à cette soirée aussi, mais je pense que j'ai quelque chose d'utile à ajouter: o).

J'ai créé un UIButton sous classe dont le but est de pouvoir choisir la disposition de l'image du bouton, verticalement ou horizontalement.

Cela signifie que vous pouvez créer ce type de boutons: différents types de boutons

Voici les détails sur la façon de créer ces boutons avec ma classe:

func makeButton (imageVerticalAlignment:LayoutableButton.VerticalAlignment, imageHorizontalAlignment:LayoutableButton.HorizontalAlignment, title:String) -> LayoutableButton {
    let button = LayoutableButton ()

    button.imageVerticalAlignment = imageVerticalAlignment
    button.imageHorizontalAlignment = imageHorizontalAlignment

    button.setTitle(title, for: .normal)

    // add image, border, ...

    return button
}

let button1 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .left, title: "button1")
let button2 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .right, title: "button2")
let button3 = makeButton(imageVerticalAlignment: .top, imageHorizontalAlignment: .center, title: "button3")
let button4 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button4")
let button5 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button5")
button5.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

Pour ce faire, j'ai ajouté 2 attributs: imageVerticalAlignmentetimageHorizontalAlignment . Bien sûr, si votre bouton n'a qu'une image ou un titre ... n'utilisez pas du tout cette classe!

J'ai également ajouté un attribut nommé imageToTitleSpacing qui vous permet d'ajuster l'espace entre le titre et l'image.

Cette classe fait de son mieux pour être compatible si vous voulez l'utiliser imageEdgeInsets, titleEdgeInsetset contentEdgeInsetsdirectement ou en combinaison avec les nouveaux attributs de mise en page.

Comme @ravron nous l'explique, je fais de mon mieux pour rendre le bord du contenu du bouton correct (comme vous pouvez le voir avec les bordures rouges).

Vous pouvez également l'utiliser dans Interface Builder:

  1. Créer un bouton UIB
  2. Changer la classe de bouton
  3. Ajustez les attributs de mise en page en utilisant "centre", "haut", "bas", "gauche" ou "droite" attributs de bouton

Voici le code ( gist ):

@IBDesignable
class LayoutableButton: UIButton {

    enum VerticalAlignment : String {
        case center, top, bottom, unset
    }


    enum HorizontalAlignment : String {
        case center, left, right, unset
    }


    @IBInspectable
    var imageToTitleSpacing: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }


    var imageVerticalAlignment: VerticalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    var imageHorizontalAlignment: HorizontalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageVerticalAlignment' instead.")
    @IBInspectable
    var imageVerticalAlignmentName: String {
        get {
            return imageVerticalAlignment.rawValue
        }
        set {
            if let value = VerticalAlignment(rawValue: newValue) {
                imageVerticalAlignment = value
            } else {
                imageVerticalAlignment = .unset
            }
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageHorizontalAlignment' instead.")
    @IBInspectable
    var imageHorizontalAlignmentName: String {
        get {
            return imageHorizontalAlignment.rawValue
        }
        set {
            if let value = HorizontalAlignment(rawValue: newValue) {
                imageHorizontalAlignment = value
            } else {
                imageHorizontalAlignment = .unset
            }
        }
    }

    var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var contentEdgeInsets: UIEdgeInsets {
        get {
            return super.contentEdgeInsets
        }
        set {
            super.contentEdgeInsets = newValue
            self.extraContentEdgeInsets = newValue
        }
    }

    var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var imageEdgeInsets: UIEdgeInsets {
        get {
            return super.imageEdgeInsets
        }
        set {
            super.imageEdgeInsets = newValue
            self.extraImageEdgeInsets = newValue
        }
    }

    var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var titleEdgeInsets: UIEdgeInsets {
        get {
            return super.titleEdgeInsets
        }
        set {
            super.titleEdgeInsets = newValue
            self.extraTitleEdgeInsets = newValue
        }
    }

    //Needed to avoid IB crash during autolayout
    override init(frame: CGRect) {
        super.init(frame: frame)
    }


    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.imageEdgeInsets = super.imageEdgeInsets
        self.titleEdgeInsets = super.titleEdgeInsets
        self.contentEdgeInsets = super.contentEdgeInsets
    }

    override func layoutSubviews() {
        if let imageSize = self.imageView?.image?.size,
            let font = self.titleLabel?.font,
            let textSize = self.titleLabel?.attributedText?.size() ?? self.titleLabel?.text?.size(attributes: [NSFontAttributeName: font]) {

            var _imageEdgeInsets = UIEdgeInsets.zero
            var _titleEdgeInsets = UIEdgeInsets.zero
            var _contentEdgeInsets = UIEdgeInsets.zero

            let halfImageToTitleSpacing = imageToTitleSpacing / 2.0

            switch imageVerticalAlignment {
            case .bottom:
                _imageEdgeInsets.top = (textSize.height + imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (-textSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (-imageSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (imageSize.height + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .top:
                _imageEdgeInsets.top = (-textSize.height - imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (textSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (imageSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (-imageSize.height - imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .center:
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
                break
            case .unset:
                break
            }

            switch imageHorizontalAlignment {
            case .left:
                _imageEdgeInsets.left = -halfImageToTitleSpacing
                _imageEdgeInsets.right = halfImageToTitleSpacing
                _titleEdgeInsets.left = halfImageToTitleSpacing
                _titleEdgeInsets.right = -halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .right:
                _imageEdgeInsets.left = textSize.width + halfImageToTitleSpacing
                _imageEdgeInsets.right = -textSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.left = -imageSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.right = imageSize.width + halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .center:
                _imageEdgeInsets.left = textSize.width / 2.0
                _imageEdgeInsets.right = -textSize.width / 2.0
                _titleEdgeInsets.left = -imageSize.width / 2.0
                _titleEdgeInsets.right = imageSize.width / 2.0
                _contentEdgeInsets.left = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
                _contentEdgeInsets.right = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
            case .unset:
                break
            }

            _contentEdgeInsets.top += extraContentEdgeInsets.top
            _contentEdgeInsets.bottom += extraContentEdgeInsets.bottom
            _contentEdgeInsets.left += extraContentEdgeInsets.left
            _contentEdgeInsets.right += extraContentEdgeInsets.right

            _imageEdgeInsets.top += extraImageEdgeInsets.top
            _imageEdgeInsets.bottom += extraImageEdgeInsets.bottom
            _imageEdgeInsets.left += extraImageEdgeInsets.left
            _imageEdgeInsets.right += extraImageEdgeInsets.right

            _titleEdgeInsets.top += extraTitleEdgeInsets.top
            _titleEdgeInsets.bottom += extraTitleEdgeInsets.bottom
            _titleEdgeInsets.left += extraTitleEdgeInsets.left
            _titleEdgeInsets.right += extraTitleEdgeInsets.right

            super.imageEdgeInsets = _imageEdgeInsets
            super.titleEdgeInsets = _titleEdgeInsets
            super.contentEdgeInsets = _contentEdgeInsets

        } else {
            super.imageEdgeInsets = extraImageEdgeInsets
            super.titleEdgeInsets = extraTitleEdgeInsets
            super.contentEdgeInsets = extraContentEdgeInsets
        }

        super.layoutSubviews()
    }
}
gbitaudeau
la source
1
Je corrige quelques trucs, pour ne pas casser l'IB avec error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros
@nebiros pouvez-vous expliquer ce qui ne va pas et comment y remédier, s'il vous plaît?
gbitaudeau
@gbitaudeau quand je copie et colle le script, j'ai eu cette erreur, error: IB Designables: Failed to update auto layout status: The agent crashedparce que ce init(frame: CGRect)n'était pas surchargé, aussi, j'ajoute une @availableannotation…, vous pouvez diff -Naursi vous voulez, ;-)
nebiros
9

Un petit ajout à la réponse de Riley Avron aux changements de paramètres régionaux du compte:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.sharedApplication().userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .LeftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}
orxelme
la source
6

Swift 4.x

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.shared.userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .leftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

Utilisation :

button.centerTextAndImage(spacing: 10.0)
Hemang
la source
Comment utiliser avec une taille d'image personnalisée?
midhun p
2

J'écris du code bewlow. Cela fonctionne bien dans la version du produit. Supprot Swift 4.2 +

extension UIButton{
 enum ImageTitleRelativeLocation {
    case imageUpTitleDown
    case imageDownTitleUp
    case imageLeftTitleRight
    case imageRightTitleLeft
}
 func centerContentRelativeLocation(_ relativeLocation: 
                                      ImageTitleRelativeLocation,
                                   spacing: CGFloat = 0) {
    assert(contentVerticalAlignment == .center,
           "only works with contentVerticalAlignment = .center !!!")

    guard (title(for: .normal) != nil) || (attributedTitle(for: .normal) != nil) else {
        assert(false, "TITLE IS NIL! SET TITTLE FIRST!")
        return
    }

    guard let imageSize = self.currentImage?.size else {
        assert(false, "IMGAGE IS NIL! SET IMAGE FIRST!!!")
        return
    }
    guard let titleSize = titleLabel?
        .systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) else {
            assert(false, "TITLELABEL IS NIL!")
            return
    }

    let horizontalResistent: CGFloat
    // extend contenArea in case of title is shrink
    if frame.width < titleSize.width + imageSize.width {
        horizontalResistent = titleSize.width + imageSize.width - frame.width
        print("horizontalResistent", horizontalResistent)
    } else {
        horizontalResistent = 0
    }

    var adjustImageEdgeInsets: UIEdgeInsets = .zero
    var adjustTitleEdgeInsets: UIEdgeInsets = .zero
    var adjustContentEdgeInsets: UIEdgeInsets = .zero

    let verticalImageAbsOffset = abs((titleSize.height + spacing) / 2)
    let verticalTitleAbsOffset = abs((imageSize.height + spacing) / 2)

    switch relativeLocation {
    case .imageUpTitleDown:

        adjustImageEdgeInsets.top = -verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageDownTitleUp:
        adjustImageEdgeInsets.top = verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = -verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageLeftTitleRight:
        adjustImageEdgeInsets.left = -spacing / 2
        adjustImageEdgeInsets.right = spacing / 2

        adjustTitleEdgeInsets.left = spacing / 2
        adjustTitleEdgeInsets.right = -spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    case .imageRightTitleLeft:
        adjustImageEdgeInsets.left = titleSize.width + spacing / 2
        adjustImageEdgeInsets.right = -titleSize.width - spacing / 2

        adjustTitleEdgeInsets.left = -imageSize.width - spacing / 2
        adjustTitleEdgeInsets.right = imageSize.width + spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    }

    imageEdgeInsets = adjustImageEdgeInsets
    titleEdgeInsets = adjustTitleEdgeInsets
    contentEdgeInsets = adjustContentEdgeInsets

    setNeedsLayout()
}
}
Jules
la source
1

Voici un exemple simple de la façon d'utiliser imageEdgeInsets Cela rendra un bouton 30x30 avec une zone pouvant être frappée 10 pixels plus grand tout autour (50x50)

    var expandHittableAreaAmt : CGFloat = 10
    var buttonWidth : CGFloat = 30
    var button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
    button.frame = CGRectMake(0, 0, buttonWidth+expandHittableAreaAmt, buttonWidth+expandHittableAreaAmt)
    button.imageEdgeInsets = UIEdgeInsetsMake(expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt)
    button.setImage(UIImage(named: "buttonImage"), forState: .Normal)
    button.addTarget(self, action: "didTouchButton:", forControlEvents:.TouchUpInside)
Harris
la source
0

Une manière élégante dans Swift 3 et mieux comprendre:

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 40
    let imgWidth:CGFloat = 24
    let imgHeight:CGFloat = 24
    return CGRect(x: leftMargin, y: (contentRect.size.height-imgHeight) * 0.5, width: imgWidth, height: imgHeight)
}

override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 80
    let rightMargin:CGFloat = 80
    return CGRect(x: leftMargin, y: 0, width: contentRect.size.width-leftMargin-rightMargin, height: contentRect.size.height)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 10
    let rightMargin:CGFloat = 10
    let topMargin:CGFloat = 10
    let bottomMargin:CGFloat = 10
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
override func contentRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 5
    let rightMargin:CGFloat = 5
    let topMargin:CGFloat = 5
    let bottomMargin:CGFloat = 5
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
teonicel
la source
-1

La version rapide de la solution 4.2 serait la suivante:

let spacing: CGFloat = 10 // the amount of spacing to appear between image and title
self.button?.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
self.button?.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)
Soheil Novinfard
la source