iPhone UIButton - posição da imagem

116

Tenho um UIButtoncom o texto "Explorar o aplicativo" e UIImage(>) Interface Buildernele se parece com:

[ (>) Explore the app ]

Mas preciso colocar isso UIImageDEPOIS do texto:

[ Explore the app (>) ]

Como posso mover o UIImagepara a direita?

Pavel Yakimenko
fonte
Você também pode usar o Interface Builder. Confira minha resposta aqui: stackoverflow.com/questions/2765024/…
n8tr
1
Basta usar esta subclasse com algumas linhas de código: github.com/k06a/RTLButton
k06a

Respostas:

161

Minha solução para isso é bastante simples

[button sizeToFit];
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width, 0, button.imageView.frame.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width, 0, -button.titleLabel.frame.size.width);
Dividido
fonte
4
Isso funciona muito bem! É difícil entender o conceito de inserção de borda. Alguma ideia de por que precisamos definir as bordas esquerda e direita? Teoricamente, se eu mover o título para a esquerda e a imagem para a direita, isso será o suficiente. Por que preciso definir a esquerda e a direita?
PokerIncome.com
3
A MELHOR SOLUÇÃO QUE
ACHEI
2
Essa resposta funciona perfeitamente. A resposta aceita está correta e aponta para os documentos de API corretos, mas esta é a solução de copiar e colar para fazer o que o OP solicitou.
mclaughlinj
Torna-se um pouco complicado quando você começa a alterar o texto do botão. A solução com subclasse funcionou melhor para mim neste caso.
Brad Goss,
Obrigado por me mostrar que você tem que aplicar larguras iguais para os lados esquerdo e direito, eu não entendo 100% como funciona (porque às vezes isso afeta o tamanho, bem como a origem), mas tenho sido capaz de resolver problemas semelhantes usando este método.
Toby
128

No iOS 9 em diante, parece que uma maneira simples de conseguir isso é forçar a semântica da visualização.

insira a descrição da imagem aqui

Ou programaticamente, usando:

button.semanticContentAttribute = .ForceRightToLeft
Alvivi
fonte
4
Por mais que tenha gostado da sua resposta (+1), odeio dizer que pode não ser a maneira "correta" de fazer isso, mas é uma das mais fáceis.
farzadshbfn
@farzadshbfn você está certo. Troquei essa palavra por "simples", parece mais consistente.
Alvivi de
@farzadshbfn Por que essa não seria a maneira "correta" de fazer?
Samman Bikram Thapa
3
@SammanBikramThapa IMHO a maneira correta seria criar uma subclasse do UIButton e substituir layoutSubviews e respeitar semanticContentAttributeem nossa lógica de layout, em vez de mudar a semanticContentAttributesi mesmo. (mudar a abordagem semântica, não funcionará bem com internacionalização)
farzadshbfn
@farzadshbfn está totalmente certo. Não importa se essa resposta marcar mais pontos, ela não está correta - pode interromper o UX da interface da direita para a esquerda.
Pavel Yakimenko
86

Defina o imageEdgeInsete titleEdgeInsetpara mover os componentes dentro de sua imagem. Você também pode criar um botão usando os gráficos em tamanho real e usá-los como imagem de fundo para o botão (depois usar titleEdgeInsetspara mover o título).

Steven Canfield
fonte
8
É muito menos código simplesmente definir as inserções do que implementar uma subclasse. Este é o ponto principal das inserções. Manipular frames para subvisualizações (que você não criou) parece mais um hack.
Kim André Sand
6
@kimsnarf, realmente? Dá muito menos trabalho (e menos hack) para ajustar as inserções sempre que você faz uma pequena alteração no tamanho da imagem ou no comprimento do título?
Kirk Woll de
54

A resposta de Raymond W é melhor aqui. Subclasse UIButton com layoutSubviews customizados. Extremamente simples de fazer, aqui está uma implementação de layoutSubviews que funcionou para mim:

- (void)layoutSubviews
{
    // Allow default layout, then adjust image and label positions
    [super layoutSubviews];

    UIImageView *imageView = [self imageView];
    UILabel *label = [self titleLabel];

    CGRect imageFrame = imageView.frame;
    CGRect labelFrame = label.frame;

    labelFrame.origin.x = imageFrame.origin.x;
    imageFrame.origin.x = labelFrame.origin.x + CGRectGetWidth(labelFrame);

    imageView.frame = imageFrame;
    label.frame = labelFrame;
}
Chris Miles
fonte
2
Desta forma é melhor no caso de você precisar gerenciar muitos botões, mas eu preciso alterar apenas um botão :)
Pavel Yakimenko
2
Se a imagem do botão for nula, os resultados do rótulo serão perdidos, provavelmente porque o UIImageView não foi inserido (testado no iOS6.0). Você deve considerar a edição de quadros apenas se imageView.image não for nulo.
Scakko de
2
Eu sugeriria a seguinte melhoria para esta resposta para que ambas as visualizações fiquem centralizadas: 'CGFloat cumulativeWidth = CGRectGetWidth (imageFrame) + CGRectGetWidth (labelFrame) + 10; CGFloat excessWidth = CGRectGetWidth (self.bounds) - cumulativeWidth; labelFrame.origin.x = excessWidth / 2; imageFrame.origin.x = CGRectGetMaxX (labelFrame) + 10; '
i-konov
Isso quebra no iOS 7 para mim. Alguém mais? Funciona bem no iOS 8.
rounak de
1
Não suporte iOS7 e seu problema desaparecerá. Você não deve supoprt de qualquer maneira.
Gil Sand
30

E quanto a subclassificação UIButtone substituição layoutSubviews?

Em seguida, o pós-processamento dos locais de self.imageView&self.titleLabel

Raymond W
fonte
4
Isso é muito mais fácil do que todos os outros conselhos (como o acima) sobre como tentar colocar tudo corretamente usando inserções ajustadas manualmente.
Steven Kramer
13

Outra maneira simples (que NÃO é apenas iOS 9) é criar uma subclasse de UIButton para substituir esses dois métodos

override func titleRectForContentRect(contentRect: CGRect) -> CGRect {
    var rect = super.titleRectForContentRect(contentRect)
    rect.origin.x = 0
    return rect
}

override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
    var rect = super.imageRectForContentRect(contentRect)
    rect.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(rect)
    return rect
}

contentEdgeInsets já é levado em consideração usando super.

DrAL3X
fonte
Obrigado! Gosto disso muito mais do que das respostas que subclassificam e substituem layoutSubviews () :)
Joe Susnick
1
A melhor maneira de fazer isso na minha humilde opinião :)
DrPatience
9

Forçar 'da direita para a esquerda' para o botão não é uma opção se o seu aplicativo suportar 'da esquerda para a direita' e 'da direita para a esquerda'.

A solução que funcionou para mim é uma subclasse que pode ser adicionada ao botão no Storyboard e funciona bem com restrições (testado no iOS 11):

class ButtonWithImageAtEnd: UIButton {

    override func layoutSubviews() {
        super.layoutSubviews()

        if let imageView = imageView, let titleLabel = titleLabel {
            let padding: CGFloat = 15
            imageEdgeInsets = UIEdgeInsets(top: 5, left: titleLabel.frame.size.width+padding, bottom: 5, right: -titleLabel.frame.size.width-padding)
            titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageView.frame.width, bottom: 0, right: imageView.frame.width)
        }

    }

}

Onde 'padding' seria o espaço entre o título e a imagem.

Luisma
fonte
Claro que .forceRightToLefté uma opção! Basta usar o valor oposto ( .forceLeftToRight) se UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft.
manmal
5

Em Swift:

override func layoutSubviews(){
    super.layoutSubviews()

    let inset: CGFloat = 5

    if var imageFrame = self.imageView?.frame,
       var labelFrame = self.titleLabel?.frame {

       let cumulativeWidth = imageFrame.width + labelFrame.width + inset
       let excessiveWidth = self.bounds.width - cumulativeWidth
       labelFrame.origin.x = excessiveWidth / 2
       imageFrame.origin.x = labelFrame.origin.x + labelFrame.width + inset

       self.imageView?.frame = imageFrame
       self.titleLabel?.frame = labelFrame  
    }
}
ChikabuZ
fonte
4

Construindo a resposta por @split ...

A resposta é fantástica, mas ignora o fato de que o botão pode ter uma imagem personalizada e inserções de borda de título que são definidas de antemão (por exemplo, no storyboard).

Por exemplo, você pode querer que a imagem tenha algum preenchimento nas partes superior e inferior do contêiner, mas ainda assim mova a imagem para o lado direito do botão.

Eu estendi o conceito com este método: -

- (void) moveImageToRightSide {
    [self sizeToFit];

    CGFloat titleWidth = self.titleLabel.frame.size.width;
    CGFloat imageWidth = self.imageView.frame.size.width;
    CGFloat gapWidth = self.frame.size.width - titleWidth - imageWidth;
    self.titleEdgeInsets = UIEdgeInsetsMake(self.titleEdgeInsets.top,
                                            -imageWidth + self.titleEdgeInsets.left,
                                            self.titleEdgeInsets.bottom,
                                            imageWidth - self.titleEdgeInsets.right);

    self.imageEdgeInsets = UIEdgeInsetsMake(self.imageEdgeInsets.top,
                                            titleWidth + self.imageEdgeInsets.left + gapWidth,
                                            self.imageEdgeInsets.bottom,
                                            -titleWidth + self.imageEdgeInsets.right - gapWidth);
}
BFar
fonte
2
// Get the size of the text and image
CGSize buttonLabelSize = [[self.button titleForState:UIControlStateNormal] sizeWithFont:self.button.titleLabel.font];
CGSize buttonImageSize = [[self.button imageForState:UIControlStateNormal] size];

// You can do this line in the xib too:
self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;

// Adjust Edge Insets according to the above measurement. The +2 adds a little space 
self.button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -(buttonLabelSize.width+2));
self.button.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, buttonImageSize.width+2);

Isso cria um botão alinhado à direita, assim:

[           button label (>)]

O botão não ajusta sua largura de acordo com o contexto, então um espaço aparecerá à esquerda do rótulo. Você pode resolver isso calculando a largura do quadro do botão em buttonLabelSize.width e em buttonImageSize.width.

Caliss
fonte
1
button.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
Todd Vanderlin
fonte
0

Esta solução funciona com iOS 7 e superior

Apenas subclasse UIButton

@interface UIButton (Image)

- (void)swapTextWithImage;

@end

@implementation UIButton (Image)

- (void)swapTextWithImage {
   const CGFloat kDefaultPadding = 6.0f;
   CGSize buttonSize = [self.titleLabel.text sizeWithAttributes:@{
                                                               NSFontAttributeName:self.titleLabel.font
                                                               }];

   self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView.frame.size.width, 0, self.imageView.frame.size.width);
   self.imageEdgeInsets = UIEdgeInsetsMake(0, buttonSize.width + kDefaultPadding, 0, -buttonSize.width); 
}

@end

Uso (em algum lugar da sua classe):

[self.myButton setTitle:@"Any text" forState:UIControlStateNormal];
[self.myButton swapTextWithImage];
Pavel Volobuev
fonte
0

Com base nas respostas anteriores. Se você quiser ter uma margem entre o ícone e o título do botão, o código deve mudar um pouco para evitar a flutuação do rótulo e do ícone acima dos limites dos botões de tamanho intrínseco.

let margin = CGFloat(4.0)
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width, 0, button.imageView.frame.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width, 0, -button.titleLabel.frame.size.width)
button.contentEdgeInsets = UIEdgeInsetsMake(0, margin, 0, margin)

A última linha de código é importante para o cálculo do tamanho do conteúdo intrinsecamente para o layout automático.

Kie
fonte
0

Aqui está minha própria maneira de fazer as coisas, (após cerca de 10 anos)

  1. Subclasse de UIButton (Button, já que vivemos na era Swift)
  2. Coloque uma imagem e um rótulo em uma exibição de pilha.
class CustomButton: Button {

    var didLayout: Bool = false // The code must be called only once

    override func layoutSubviews() {
        super.layoutSubviews()
        if !didLayout, let imageView = imageView, let titleLabel = titleLabel {
            didLayout = true
            let stack = UIStackView(arrangedSubviews: [titleLabel, imageView])
            addSubview(stack)
            stack.edgesToSuperview() // I use TinyConstraints library. You could handle the constraints directly
            stack.axis = .horizontal
        }
    }
}
Pavel Yakimenko
fonte
0

Solução de linha única em Swift:

// iOS 9 and Onwards
button.semanticContentAttribute = .forceRightToLeft
Sunil Targe
fonte
Não é bom, pois quebra o layout para todos os usuários RTL. Você precisará forçar da esquerda para a direita para eles. É melhor você tentar outra solução a partir daqui.
Pavel Yakimenko