iPhone UIButton - posición de la imagen

116

Tengo un UIButtontexto con "Explore la aplicación" y UIImage(>) En Interface Builderse ve así:

[ (>) Explore the app ]

Pero necesito colocar esto UIImageDESPUÉS del texto:

[ Explore the app (>) ]

¿Cómo puedo mover UIImagehacia la derecha?

Pavel Yakimenko
fuente
También puede utilizar Interface Builder. Mira mi respuesta aquí: stackoverflow.com/questions/2765024/…
n8tr
1
Simplemente use esta subclase con algunas filas de código: github.com/k06a/RTLButton
k06a

Respuestas:

161

Mi solución a esto es bastante simple

[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);
División
fuente
4
¡Esto funciona muy bien! Es difícil entender el concepto de inserción de borde. ¿Alguna idea de por qué necesitamos colocar el borde derecho e izquierdo insertado? Teóricamente, si muevo el título a la izquierda y la imagen a la derecha, eso sería suficiente. ¿Por qué necesito configurar tanto a la izquierda como a la derecha?
PokerIncome.com
3
LA MEJOR SOLUCIÓN QUE HE ENCONTRADO
Bimawa
2
Esta respuesta funciona perfectamente. La respuesta aceptada es correcta y apunta a los documentos API correctos, pero esta es la solución de copiar y pegar para hacer lo que solicitó el OP.
mclaughlinj
Se vuelve un poco complicado cuando comienzas a cambiar el texto del botón. La solución de subclases funcionó mejor para mí en este caso.
Brad Goss
Gracias por mostrarme que tiene que aplicar anchos iguales a los lados izquierdo y derecho, no entiendo al 100% cómo funciona (porque a veces afectará el tamaño y el origen) pero he podido resolver problemas similares usando este método.
Toby
128

En iOS 9 en adelante, parece que una forma sencilla de conseguirlo es forzar la semántica de la vista.

ingrese la descripción de la imagen aquí

O programáticamente, usando:

button.semanticContentAttribute = .ForceRightToLeft
Alvivi
fuente
4
Por mucho que me gustó tu respuesta (+1), odio decir que puede que no sea la forma "correcta" de hacerlo, pero es una de las más fáciles.
farzadshbfn
@farzadshbfn tienes razón. Cambié esa palabra por "simple", parece más consistente.
Alvivi
@farzadshbfn ¿Por qué no sería esta la forma "correcta" de hacerlo?
Samman Bikram Thapa
3
@SammanBikramThapa En mi humilde opinión, la forma correcta sería subclasificar el UIButton y anular layoutSubviews y respetar semanticContentAttributenuestra lógica de diseño, en lugar de cambiarse a semanticContentAttributesí mismo. (cambio de enfoque semántico, no funcionará bien con la internacionalización)
farzadshbfn
@farzadshbfn tiene toda la razón. No importa si esta respuesta obtiene la mayoría de los puntos, no es correcta: puede romper la UX para la interfaz de derecha a izquierda.
Pavel Yakimenko
86

Configure imageEdgeInsety titleEdgeInsetpara mover los componentes dentro de su imagen. También puede crear un botón con esos gráficos de tamaño completo y usarlo como imagen de fondo para el botón (luego usarlo titleEdgeInsetspara mover el título).

Steven Canfield
fuente
8
Es mucho menos código simplemente establecer las inserciones que implementar una subclase. Este es el objetivo de las inserciones. Manipular marcos para sub vistas (que no ha creado) se siente más como un truco.
Kim André Sand
6
@kimsnarf, ¿de verdad? ¿Es mucho menos trabajo (y menos complicado) modificar las inserciones cada vez que realiza un cambio menor en el tamaño de la imagen o en la longitud del título?
Kirk Woll
54

La respuesta de Raymond W. es la mejor aquí. Subclase UIButton con diseño personalizado Subvistas. Extremadamente simple de hacer, aquí hay una implementación de layoutSubviews que funcionó para mí:

- (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
fuente
2
De esta manera es mejor en el caso de que necesite administrar muchos botones, pero necesito cambiar solo un botón :)
Pavel Yakimenko
2
Si la imagen del botón es nula, la etiqueta se pierde, probablemente porque el UIImageView no está insertado (probado en iOS6.0). Debería considerar editar marcos solo si imageView.image no es nulo.
Scakko
2
Sugeriría la siguiente mejora a esta respuesta para que ambas vistas permanezcan centradas: 'CGFloat cumulativeWidth = CGRectGetWidth (imageFrame) + CGRectGetWidth (labelFrame) + 10; CGFloat robustWidth = CGRectGetWidth (self.bounds) - cumulativeWidth; labelFrame.origin.x = excellentWidth / 2; imageFrame.origin.x = CGRectGetMaxX (labelFrame) + 10; '
i-konov
Esto se rompe en iOS 7 para mí. ¿Alguien mas? Funciona bien en iOS 8.
rounak
1
No admita iOS7 en absoluto y su problema desaparecerá. De todos modos, no deberías supoprtlo.
Gil Sand
30

¿Qué pasa con la subclasificación UIButtony la anulación layoutSubviews?

Luego, posprocesando las ubicaciones de self.imageView&self.titleLabel

Raymond W
fuente
4
Esto es mucho más fácil que todos los demás consejos (como el anterior) sobre cómo tratar de colocar todo correctamente utilizando inserciones ajustadas a mano.
Steven Kramer
13

Otra forma simple (que NO es solo iOS 9) es subclase UIButton para anular estos dos 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 ya se tiene en cuenta al usar super.

DrAL3X
fuente
¡Gracias! Me gusta esto mucho más que las respuestas que subclasifican y anulan el diseño Subvistas () :)
Joe Susnick
1
La mejor manera de hacerlo, en mi humilde opinión :)
DrPatience
9

Forzar "de derecha a izquierda" para el botón no es una opción si su aplicación admite tanto "de izquierda a derecha" como "de derecha a izquierda".

La solución que funcionó para mí es una subclase que se puede agregar al botón en el Storyboard y funciona bien con restricciones (probado en 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)
        }

    }

}

Donde 'padding' sería el espacio entre el título y la imagen.

Luisma
fuente
¡Por supuesto que .forceRightToLeftes una opción! Simplemente use el valor opuesto ( .forceLeftToRight) si UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft.
manmal
5

En 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
fuente
4

Partiendo de la respuesta de @split ...

La respuesta es fantástica, pero ignora el hecho de que el botón puede tener insertos personalizados en el borde del título y la imagen que se configuran de antemano (por ejemplo, en el guión gráfico).

Por ejemplo, es posible que desee que la imagen tenga algo de relleno en la parte superior e inferior del contenedor, pero aún así mover la imagen al lado derecho del botón.

Amplié el concepto con 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
fuente
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);

Esto crea un botón alineado a la derecha, así:

[           button label (>)]

El botón no ajusta su ancho según el contexto, por lo que aparecerá un espacio a la izquierda de la etiqueta. Puede resolver esto calculando el ancho del marco del botón a partir de buttonLabelSize.width y buttonImageSize.width.

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

Esta solución funciona con iOS 7 y superior

Solo subclase 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 (en algún lugar de tu clase):

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

Basándose en respuestas anteriores. Si desea tener un margen entre el icono y el título del botón, el código debe cambiar un poco para evitar que la etiqueta y el icono floten por encima de los límites de los botones de tamaño 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)

La última línea de código es importante para el cálculo intrínsecamente del tamaño del contenido para el diseño automático.

Kie
fuente
0

Esta es mi propia forma de hacer las cosas (después de unos 10 años)

  1. Subclase de UIButton (Botón, ya que vivimos en la era Swift)
  2. Coloque una imagen y una etiqueta en una vista de pila.
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
fuente
0

Solución de línea única en Swift:

// iOS 9 and Onwards
button.semanticContentAttribute = .forceRightToLeft
Sunil Targe
fuente
No es bueno ya que romperá el diseño para todos los usuarios de RTL. Necesitarás forzar de izquierda a derecha para ellos. Será mejor que pruebes otra solución desde aquí.
Pavel Yakimenko