Alinhando texto e imagem no UIButton com imageEdgeInsets e titleEdgeInsets

248

Gostaria de colocar um ícone à esquerda das duas linhas de texto, de modo que haja cerca de 2-3 pixels de espaço entre a imagem e o início do texto. O controle em si é o centro alinhado horizontalmente (definido pelo Interface Builder)

O botão seria semelhante a isto:

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

Eu estou tentando configurar isso com contentEdgeInset, imageEdgeInsets e titleEdgeInsets sem sucesso. Entendo que um valor negativo expande a borda, enquanto um valor positivo diminui para movê-lo para mais perto do centro.

Eu tentei:

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

mas isso não é exibido corretamente. Estive ajustando os valores, mas passando de -5 para -10 no valor inserido à esquerda não parece movê-lo da maneira esperada. -10 vai arrastar o texto para a esquerda, então eu esperava -5 para arrastá-lo até a metade do lado esquerdo, mas não o faz.

Qual é a lógica por trás das inserções? Não estou familiarizado com canais de imagem e terminologia relacionada.

Eu usei essa pergunta SO como referência, mas algo sobre meus valores não está certo. UIButton: como centralizar uma imagem e um texto usando imageEdgeInsets e titleEdgeInsets?

Justin Galzic
fonte

Respostas:

392

Concordo com a documentação imageEdgeInsetse titleEdgeInsetsdeveria ser melhor, mas descobri como obter o posicionamento correto sem recorrer a tentativa e erro.

A idéia geral está aqui nesta pergunta , mas era se você queria texto e imagem centralizados. Não queremos que a imagem e o texto sejam centralizados individualmente, queremos que a imagem e o texto sejam centralizados juntos como uma única entidade. Isso é de fato o que o UIButton já faz, então precisamos simplesmente ajustar o espaçamento.

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

Também transformei isso em uma categoria para UIButton, para que seja fácil de usar:

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

Então agora tudo o que tenho a fazer é:

[button centerButtonAndImageWithSpacing:10];

E recebo o que preciso sempre. Chega de mexer com as inserções de borda manualmente.

EDIT: Troca de imagem e texto

Em resposta a @Javal nos comentários

Usando esse mesmo mecanismo, podemos trocar a imagem e o texto. Para realizar a troca, basta usar um espaçamento negativo, mas também incluir a largura do texto e da imagem. Isso exigirá que os quadros sejam conhecidos e o layout já seja executado.

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

É claro que você provavelmente desejará criar um bom método para isso, potencialmente adicionando um método de segunda categoria, que é deixado como um exercício para o leitor.

Kekoa
fonte
Se eu tiver títulos diferentes para normal e realçado, como posso centralizá-lo novamente quando o usuário destaca e desmarca o botão?
user102008
@ user102008 Títulos diferentes? Ou apenas cores diferentes? O posicionamento não deve mudar se você estiver usando [UIButton setTitleColor:forState:], ou mesmo [UIButton setTitle:forState:].
Kekoa
5
Como você corrige [sizeToFit do botão] para que seja dimensionado corretamente?
RonLugge
@ RonLugge Não sei por que você precisa do sizeToFit, por isso não posso responder à sua pergunta. Funciona bem para mim sem usar sizeToFit.
Kekoa
Preciso de sizeToFit porque os botões / texto são dinâmicos, portanto, preciso dimensionar o botão para caber no rótulo (definido pelo usuário). O problema é que ele não compensa o espaço adicionado. Acabei substituindo-lo, e aumentando manualmente a largura da moldura por 10.
RonLugge
395

Estou um pouco atrasado para esta festa, mas acho que tenho algo útil a acrescentar.

A resposta de Kekoa é ótima, mas, como RonLugge menciona, ele pode fazer com que o botão não seja mais respeitado sizeToFitou, mais importante, pode fazer com que o botão recorte seu conteúdo quando ele é intrinsecamente dimensionado. Caramba!

Primeiro, porém,

Uma breve explicação de como acredito imageEdgeInsetse titleEdgeInsetstrabalho:

Os documentos paraimageEdgeInsets têm o seguinte a dizer, em parte:

Use esta propriedade para redimensionar e reposicionar o retângulo de desenho efetivo para a imagem do botão. Você pode especificar um valor diferente para cada uma das quatro inserções (superior, esquerda, inferior, direita). Um valor positivo diminui ou insere essa borda - aproximando-o do centro do botão. Um valor negativo expande ou ultrapassa essa margem.

Acredito que esta documentação foi escrita imaginando que o botão não tem título, apenas uma imagem. Faz muito mais sentido pensar dessa maneira e se comporta como UIEdgeInsetscostuma fazer. Basicamente, o quadro da imagem (ou o título, com titleEdgeInsets) é movido para dentro para inserções positivas e para fora para inserções negativas.

Tá, e daí?

Estou chegando lá! Aqui está o que você tem por padrão, definindo uma imagem e um título (a borda do botão é verde apenas para mostrar onde está):

Imagem inicial;  não há espaço entre o título e a imagem.

Quando você deseja espaçar entre uma imagem e um título, sem causar esmagamento, é necessário definir quatro inserções diferentes, duas em cada imagem e título. Isso ocorre porque você não deseja alterar os tamanhos dos quadros desses elementos, mas apenas suas posições. Quando você começa a pensar dessa maneira, a mudança necessária para a excelente categoria de Kekoa fica clara:

@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

Mas espere , você diz, quando faço isso, recebo o seguinte:

O espaçamento é bom, mas a imagem e o título estão fora do quadro da vista.

Oh sim! Eu esqueci, os documentos me avisaram sobre isso. Eles dizem, em parte:

Esta propriedade é usada apenas para posicionar a imagem durante o layout. O botão não usa essa propriedade para determinar intrinsicContentSizee sizeThatFits:.

Mas não é uma propriedade que pode ajudar, e isso é contentEdgeInsets. Os documentos para isso dizem, em parte:

O botão usa essa propriedade para determinar intrinsicContentSizee sizeThatFits:.

Isso soa bem. Então, vamos ajustar a categoria mais uma vez:

@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

e o que você ganha?

O espaçamento e o quadro agora estão corretos.

Parece um vencedor para mim.


Trabalhando na Swift e não quer pensar em nada? Aqui está a versão final da extensão no 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
fonte
25
Que ótima resposta! Sim, alguns anos atrasado para a festa, mas isso resolveu o problema de intrinsicContentSizeestar incorreto, o que é muito importante nesses dias de layout automático desde que a resposta original foi aceita.
Acey
2
E se você quisesse a mesma quantidade de espaçamento entre o exterior do botão ea imagem eo rótulo, em seguida, adicione spacinga cada um dos quatro valores de self.contentEdgeInsets, assim:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut
1
Ótima resposta, pena que não funcione muito bem quando a imagem está alinhada à direita e o comprimento do texto pode variar.
Jlpiedrahita # 10/15
2
Resposta quase perfeita! A única coisa que falta é que as inserções da imagem e do título precisem ser invertidas quando executadas na interface da direita para a esquerda.
Jeeeyul #
como definir a proporção da imagem para 1 para 1
Yestay Muratov 08/12/16
39

Na interface Builder. Selecione o UIButton -> Inspetor de atributos -> Borda = Título e modifique as inserções de borda

Freeman
fonte
38

Além disso, se você quiser fazer algo semelhante a

insira a descrição da imagem aqui

Você precisa

1. Defina o alinhamento horizontal e vertical para o botão

insira a descrição da imagem aqui

  1. Encontre todos os valores necessários e defina 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)];

Isso organizará seu título e imagem no botão.

Observe também que atualize isso em cada retransmissão


Rápido

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
fonte
29

Você pode evitar muitos problemas usando isso -

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

Isso alinhará todo o seu conteúdo automaticamente à esquerda (ou onde você quiser)

Swift 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Nishant
fonte
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //
Erro de digitação
25

No Xcode 8.0, você pode simplesmente fazer isso alterando o insetstamanho do inspetor.

Selecione o UIButton -> Inspetor de atributos -> vá para o inspetor de tamanho e modifique o conteúdo, a imagem e as inserções de título.

insira a descrição da imagem aqui

E se você deseja alterar a imagem no lado direito, basta alterar a propriedade semântica para Force Right-to-leftno Inspetor de atributos.

insira a descrição da imagem aqui

Sahil
fonte
no meu caso, a imagem do botão nunca se move para o lado direito do texto com o xcode 10? você pode ajudar?
Satish Mavani
Oi Satish, isso também está funcionando bem com o xcode 10. Espero que você esteja definindo a imagem e não a imagem de fundo e também pode alterar os insetos da imagem usando o inspetor de tamanho.
Sahil
18

Estou um pouco atrasado para esta festa também, mas acho que tenho algo útil a acrescentar: o).

Criei uma UIButtonsubclasse cujo objetivo é poder escolher onde a imagem do botão é o layout, vertical ou horizontalmente.

Isso significa que você pode criar esse tipo de botão: tipo diferente de botões

Aqui estão os detalhes sobre como criar esses botões com a minha 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)

Para fazer isso, adicionei 2 atributos: imageVerticalAlignmente imageHorizontalAlignment. Claro, se o seu botão tiver apenas uma imagem ou um título ... não use esta classe!

Também adicionei um atributo chamado imageToTitleSpacingque permite ajustar o espaço entre o título e a imagem.

Esta classe tentar o seu melhor para ser compatível, se você quer usar imageEdgeInsets, titleEdgeInsetse contentEdgeInsetsdireta ou em combinaison com os novos atributos de layout.

Como o @ravron nos explica, eu tento o meu melhor para corrigir a borda do conteúdo do botão (como você pode ver com as bordas vermelhas).

Você também pode usá-lo no Interface Builder:

  1. Crie um UIButton
  2. Alterar a classe do botão
  3. Ajuste os atributos de layout usando "centro", "superior", "inferior", "esquerda" ou "direita" atributos de botão

Aqui o código ( essência ):

@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
fonte
1
Eu conserto algumas coisas, para não interromper o IB error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros
@nebiros, você pode explicar o que está errado e como corrigi-lo, por favor?
Gbitaudeau 28/02
@gbitaudeau quando eu copiar e colar o script, eu tenho esse erro, error: IB Designables: Failed to update auto layout status: The agent crashedporque init(frame: CGRect)não foi substituído, também, acrescento @availableanotação ..., você pode diff -Naur, se quiser, ;-)
Nebiros
9

Uma pequena adição à resposta de Riley Avron para alterar as localidades da conta:

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)
    }
}
orxelm
fonte
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)
    }
}

Uso :

button.centerTextAndImage(spacing: 10.0)
Hemang
fonte
Como usar com o tamanho da imagem de custiom?
Midhun p
2

Eu escrevo código bewlow. Funciona bem na versão do produto. 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
fonte
1

Aqui está um exemplo simples de como usar o imageEdgeInsets Isso criará um botão 30x30 com uma área atingível 10 pixels maior ao redor (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
fonte
0

Uma maneira elegante no Swift 3 e melhor para entender:

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
fonte
-1

A versão rápida 4.2 da solução seria a seguinte:

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
fonte