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á):
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:
¡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
ysizeThatFits:
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
ysizeThatFits:
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?
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
Necesitas
1. Establezca la alineación horizontal y vertical para que el botón
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.
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.
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:
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:
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) }