Alinear texto e imagen en UIButton con imageEdgeInsets y titleEdgeInsets

249

Me gustaría colocar un icono a la izquierda de las dos líneas de texto de modo que haya aproximadamente 2-3 píxeles de espacio entre la imagen y el inicio del texto. El control en sí está alineado horizontalmente en el centro (establecido a través del Creador de interfaces)

El botón se parecería a algo como esto:

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

Estoy tratando de configurar esto con contentEdgeInset, imageEdgeInsets y titleEdgeInsets en vano. Entiendo que un valor negativo expande el borde mientras que un valor positivo lo reduce para acercarlo al centro.

Lo intenté:

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

pero esto no lo muestra correctamente. He estado ajustando los valores, pero pasar de decir -5 a -10 en el valor del recuadro izquierdo no parece moverlo de la manera esperada. -10 desplazará el texto completamente hacia la izquierda, así que esperaba que -5 lo desplazara a la mitad del lado izquierdo, pero no lo hace.

¿Cuál es la lógica detrás de las inserciones? No estoy familiarizado con las ubicaciones de imágenes y la terminología relacionada.

Utilicé esta pregunta SO como referencia, pero algo sobre mis valores no está bien. UIButton: ¿cómo centrar una imagen y un texto usando imageEdgeInsets y titleEdgeInsets?

Justin Galzic
fuente

Respuestas:

392

Estoy de acuerdo con la documentación imageEdgeInsetsy titleEdgeInsetsdebería ser mejor, pero descubrí cómo obtener el posicionamiento correcto sin recurrir a prueba y error.

La idea general está aquí en esta pregunta , pero eso era si querías centrar tanto el texto como la imagen. No queremos que la imagen y el texto se centren individualmente, queremos que la imagen y el texto se centren juntos como una sola entidad. De hecho, esto es lo que UIButton ya hace, así que simplemente necesitamos ajustar el espaciado.

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

También convertí esto en una categoría para UIButton, por lo que será 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

Entonces ahora todo lo que tengo que hacer es:

[button centerButtonAndImageWithSpacing:10];

Y consigo lo que necesito cada vez. No más jugar con las inserciones de borde manualmente.

EDITAR: Intercambiar imagen y texto

En respuesta a @Javal en comentarios

Usando este mismo mecanismo, podemos intercambiar la imagen y el texto. Para realizar el intercambio, simplemente use un espaciado negativo pero también incluya el ancho del texto y la imagen. Esto requerirá que se conozcan los marcos y que el diseño ya se haya realizado.

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

Por supuesto, es probable que desee hacer un buen método para esto, agregando potencialmente un método de segunda categoría, esto se deja como un ejercicio para el lector.

Kekoa
fuente
Si tengo diferentes títulos para normal y resaltado, ¿cómo puedo volver a centrarlo cuando el usuario resalta y desmarca el botón?
user102008
@ user102008 ¿Diferentes títulos por completo? O simplemente diferentes colores? El posicionamiento no debería cambiar si está utilizando [UIButton setTitleColor:forState:], o incluso [UIButton setTitle:forState:].
Kekoa
55
¿Cómo arreglas [button sizeToFit] para que se ajuste correctamente?
RonLugge
@RonLugge No estoy seguro de por qué necesita sizeToFit, por lo que no puedo responder a su pregunta. Funciona bien para mí sin usar sizeToFit.
Kekoa
Necesito sizeToFit porque los botones / texto son dinámicos, así que necesito cambiar el tamaño del botón para que se ajuste a la etiqueta (definida por el usuario). El problema es que no compensa el espacio agregado. Terminé sobreescribiéndolo y aumentando manualmente el ancho del marco en 10.
RonLugge
397

Llego un poco tarde a esta fiesta, pero creo que tengo algo útil que agregar.

La respuesta de Kekoa es excelente, pero, como menciona RonLugge, puede hacer que el botón ya no respete sizeToFito, lo que es más importante, puede hacer que el botón recorte su contenido cuando tiene un tamaño intrínseco. ¡Ay!

Primero, sin embargo,

Una breve explicación de cómo creo imageEdgeInsetsy titleEdgeInsetstrabajo:

Los documentos paraimageEdgeInsets tienen lo siguiente que decir, en parte:

Use esta propiedad para cambiar el tamaño y la posición del rectángulo de dibujo efectivo para la imagen del botón. Puede especificar un valor diferente para cada una de las cuatro inserciones (superior, izquierda, inferior, derecha). Un valor positivo reduce o inserta ese borde, acercándolo al centro del botón. Un valor negativo expande, o compensa, ese borde.

Creo que esta documentación fue escrita imaginando que el botón no tiene título, solo una imagen. Tiene mucho más sentido pensar de esta manera, y se comporta como UIEdgeInsetssuele hacerlo. Básicamente, el marco de la imagen (o el título, con titleEdgeInsets) se mueve hacia adentro para inserciones positivas y hacia afuera para inserciones negativas.

¿OK y eso qué?

¡Estoy llegando! Esto es lo que tiene de manera predeterminada, configurando una imagen y un título (el borde del botón es verde solo para mostrar dónde está):

Imagen de inicio;  No hay espacio entre el título y la imagen.

Cuando desee un espacio entre una imagen y un título, sin hacer que ninguno de los dos sea aplastado, debe establecer cuatro inserciones diferentes, dos en cada imagen y título. Esto se debe a que no desea cambiar el tamaño de los marcos de esos elementos, sino solo sus posiciones. Cuando comienzas a pensar de esta manera, el cambio necesario a la excelente categoría de Kekoa queda claro:

@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

Pero espera , dices, cuando hago eso, me sale esto:

El espaciado es bueno, pero la imagen y el título están fuera del marco de la vista.

¡Oh si! Olvidé, los documentos me advirtieron sobre esto. Dicen, en parte:

Esta propiedad se usa solo para posicionar la imagen durante el diseño. El botón no usa esta propiedad para determinar intrinsicContentSizey sizeThatFits:.

Pero no es una propiedad que ayuda lata, y eso es contentEdgeInsets. Los documentos para eso dicen, en parte:

El botón usa esta propiedad para determinar intrinsicContentSizey sizeThatFits:.

Eso suena bien. Entonces, modifiquemos la categoría una vez más:

@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

Y que obtienes

El espaciado y el marco ahora son correctos.

Me parece un ganador.


¿Trabajas en Swift y no quieres pensar en absoluto? Aquí está la versión final de la extensión en 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
fuente
25
¡Qué gran respuesta! Sí, unos años tarde a la fiesta, pero esto resolvió el problema de intrinsicContentSizeser incorrecto, lo cual es muy importante en estos días de diseño automático ya que se aceptó la respuesta original.
Acey
2
Y si desea la misma cantidad de espacio entre el exterior del botón y la imagen y la etiqueta, agregue spacinga cada uno de los cuatro valores de self.contentEdgeInsets, así:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut
1
Gran respuesta, una pena que no funcione del todo bien cuando la imagen está alineada correctamente y la longitud del texto puede variar.
jlpiedrahita
2
¡Respuesta casi perfecta! Lo único que falta es que las inserciones de imagen y título deberían invertirse cuando se ejecuta en la interfaz de derecha a izquierda.
jeeeyul
cómo establecer la relación de imagen de 1 a 1
Yestay Muratov
39

En la interfaz del generador. Seleccione el botón UIB -> Inspector de atributos -> Borde = Título y modifique las inserciones de borde

Hombre libre
fuente
38

También si quieres hacer algo similar a

ingrese la descripción de la imagen aquí

Necesitas

1.Ajuste la alineación horizontal y vertical para que el botón

ingrese la descripción de la imagen aquí

  1. Encuentre todos los valores requeridos y establezca 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)];

Esto organizará su título e imagen en el botón.

También tenga en cuenta actualizar esto en cada retransmisión


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

Puede evitar muchos problemas al usar esto:

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

Esto alineará todo su contenido automáticamente a la izquierda (o donde lo desee)

Swift 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Nishant
fuente
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //
Error tipográfico
25

En Xcode 8.0 simplemente puedes hacerlo cambiandoinsets tamaño del inspector.

Seleccione el botón UIB -> Inspector de atributos -> vaya al inspector de tamaño y modifique las inserciones de contenido, imagen y título.

ingrese la descripción de la imagen aquí

Y si desea cambiar la imagen en el lado derecho, simplemente puede cambiar la propiedad semántica Force Right-to-leften el inspector de atributos.

ingrese la descripción de la imagen aquí

Sahil
fuente
en mi caso, la imagen del botón nunca se mueve al lado derecho del texto con xcode 10? ¿puede usted ayudar?
Satish Mavani
Hola Satish, esto también funciona bien con xcode 10. Espero que esté configurando la imagen, no la imagen de fondo, y también puede cambiar los insectos de la imagen usando el inspector de tamaño.
Sahil
18

También llego un poco tarde a esta fiesta, pero creo que tengo algo útil que agregar: o).

Creé una UIButtonsubclase cuyo propósito es poder elegir dónde se ubica la imagen del botón, ya sea vertical u horizontalmente.

Significa que puedes hacer este tipo de botones: diferentes tipos de botones

Aquí los detalles sobre cómo crear estos botones con mi clase:

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 hacer eso, agregué 2 atributos: imageVerticalAlignmenty imageHorizontalAlignment. Por supuesto, si su botón solo tiene una imagen o un título ... ¡no use esta clase en absoluto!

También agregué un atributo llamado imageToTitleSpacing que le permite ajustar el espacio entre el título y la imagen.

Esta clase hace todo lo posible para ser compatible si desea usar imageEdgeInsets, titleEdgeInsetsy contentEdgeInsetsdirectamente o en combinación con los nuevos atributos de diseño.

Como nos explica @ravron, hago todo lo posible para que el borde del contenido del botón sea correcto (como puede ver con los bordes rojos).

También puede usarlo en Interface Builder:

  1. Crear un botón UIB
  2. Cambiar la clase del botón
  3. Ajuste los atributos de diseño utilizando "centro", "arriba", "abajo", "izquierda" o "derecha" atributos del botón

Aquí el código ( esencia ):

@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
fuente
1
Arreglo algunas cosas, para no romper el IB con error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros
@nebiros, ¿puedes explicar qué está mal y cómo lo arreglas, por favor?
gbitaudeau
@gbitaudeau cuando copio y pego el script, recibí ese error, error: IB Designables: Failed to update auto layout status: The agent crashedporque init(frame: CGRect)no fue anulado, también, @availablediff -Naur
agregué una
9

Una pequeña adición a la respuesta de Riley Avron a los cambios en la configuración regional de la cuenta:

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
fuente
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
fuente
¿Cómo se puede usar con el tamaño de imagen custiom?
midhun p
2

Escribo el código bewlow. Funciona bien en la versión del producto. 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
fuente
1

Aquí hay un ejemplo simple de cómo usar imageEdgeInsets. Esto hará que un botón de 30x30 con un área golpeable sea 10 píxeles más grande (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
fuente
0

Una manera elegante en Swift 3 y mejor comprensión:

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

La versión rápida de la solución 4.2 sería la siguiente:

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
fuente