Alineación de texto e imagen en UIButton con imageEdgeInsets y titleEdgeInsets

Me gustaría colocar un ícono a la izquierda de las dos líneas de texto, de modo que haya entre 2 y 3 píxeles de espacio entre la imagen y el comienzo del texto. El control en sí está alineado en el centro horizontalmente (configurado a través de Interface Builder)

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

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

Estoy intentando 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 hacia la izquierda, así que esperaba que -5 lo desplazara a la mitad del lado izquierdo, pero no es así.

¿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 es correcto. UIButton: cómo centrar una imagen y un texto usando imageEdgeInsets y titleEdgeInsets?

Acepto que la documentación sobre imageEdgeInsets y titleEdgeInsets deberí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 fue si usted quería que el texto y la imagen se centraran. 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. Esto es de hecho 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 lo convertí 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 obtengo lo que necesito todo el tiempo. No más problemas con las inserciones de borde manualmente.

EDITAR: Intercambiando 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 espacio 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, potencialmente agregando un método de segunda categoría, esto se deja como un ejercicio para el lector.

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

La respuesta de Kekoa es genial, pero, como menciona RonLugge, puede hacer que el botón ya no respete sizeToFit o, 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 que funcionan imageEdgeInsets y titleEdgeInsets :

Los documentos para imageEdgeInsets tienen lo siguiente que decir, en parte:

Utilice 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 (arriba, izquierda, abajo, derecha). Un valor positivo se reduce, o inserciones, ese borde, moviéndolo más cerca del centro del botón. Un valor negativo expande, o inicia, 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 lo UIEdgeInsets generalmente UIEdgeInsets . Básicamente, el marco de la imagen (o el título, con titleEdgeInsets ) se mueve hacia adentro para las inserciones positivas y hacia afuera para las inserciones negativas.

¿OK y eso qué?

¡Estoy llegando! Esto es lo que tiene de manera predeterminada, establecer 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 espaciado entre una imagen y un título, sin que se triture, deberá configurar cuatro inserciones diferentes, dos en cada una de las imágenes y títulos. 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 para la excelente categoría de Kekoa se vuelve 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, entiendo esto:

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

¡Oh si! Lo 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 intrinsicContentSize y sizeThatFits:

Pero hay una propiedad que puede ayudar, y eso es contentEdgeInsets . Los documentos para eso dicen, en parte:

El botón usa esta propiedad para determinar intrinsicContentSize y sizeThatFits:

Eso suena bien. Así que 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 qué obtienes?

El espaciado y el marco ahora son correctos.

Parece un ganador para mí.


¿Trabajando en Swift y no quiero 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) } } 

En Interface Builder. Seleccione el UIButton -> Atributo Inspector -> Borde = Título y modifique las inserciones de borde

También si quieres hacer algo similar a

enter image description here

Necesitas

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

enter image description here

  1. Encuentre todos los valores requeridos y configure 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 relayout


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

Puede evitar muchos problemas usando esto:

 myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; myButton.contentVerticalAlignment = UIControlContentVerticalAlignment; 

Esto alineará todo tu contenido automáticamente a la izquierda (o donde quieras)

Swift 3:

 myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left; myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; 

En Xcode 8.0 puedes simplemente hacerlo cambiando las insets en el inspector de tamaño.

Seleccione el UIButton -> Inspector de atributos -> ir al inspector de tamaño y modificar el contenido, la imagen y las inserciones de título.

enter image description here

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

enter image description here

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

UIButton una subclase UIButton cuyo propósito es poder elegir dónde se distribuye 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: imageVerticalAlignment y 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 , titleEdgeInsets y contentEdgeInsets directamente o en combinación con los nuevos atributos de diseño.

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

También puede usarlo en Interface Builder:

  1. Crear un UIButton
  2. Cambiar la clase de botón
  3. Ajuste los atributos de Layoutable usando “center”, “top”, “bottom”, “left” o “right” 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() } } 

Una pequeña adición a la respuesta de Riley Avron a los cambios en la configuración 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) } } 

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) 

Aquí hay un ejemplo simple de cómo usar imageEdgeInsets Esto hará que un botón de 30×30 con un área hittable 10 píxeles más grande en todos los sentidos (50×50)

  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) 

Una forma elegante en Swift 3 y mejor 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) }