Reproducción automática: el tamaño intrínseco de UIButton no incluye inserciones de título

196

Si tengo un UIButton arreglado usando autolayout, su tamaño se ajusta muy bien para adaptarse a su contenido.

Si configuro una imagen como button.image, el tamaño intrínseco nuevamente parece explicar esto.

Sin embargo, si modifico el titleEdgeInsetsbotón, el diseño no tiene en cuenta esto y en su lugar trunca el título del botón.

¿Cómo puedo asegurarme de que el ancho intrínseco del botón tenga en cuenta el recuadro?

ingrese la descripción de la imagen aquí

Editar:

Estoy usando lo siguiente:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

El objetivo es agregar cierta separación entre la imagen y el texto.

Ben Packard
fuente
3
¿Archivaste esto como un radar? Ciertamente parece ser un error en los cálculos de tamaño intrínseco de UIButton.
Ryan Poolos
1
Estaba listo para archivar un radar, pero este parece ser un comportamiento esperado. Esto está documentado en las propiedades * EdgeInsets de UIButton : "Las inserciones que especifique se aplican al rectángulo del título después de que ese rectángulo se haya ajustado para ajustarse al texto del botón. Por lo tanto, los valores de inserción positivos pueden en realidad recortar el texto del título. [...] el botón no usa esta propiedad para determinar intrinsicContentSize y sizeThatFits :. "
Guillaume Algis
77
@GuillaumeAlgis Yo diría que, si bien este es un comportamiento establecido, es que no es en absoluto lo que uno puede esperar que ocurra cuando se utiliza el diseño automático. He presentado un error y alentaría a otros a que también lo hagan.
memmons
Si puede vincular el error de radar aquí, ¿podemos hacer clic en él y hacer +1 en él?
gprasant
1
de la titleEdgeInsetdocumentación: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text. Entonces, al agregar un recuadro, está forzando el botón para recortar el texto con seguridad
Marco Pappalardo

Respuestas:

192

Puede resolver esto sin tener que anular ningún método o establecer una restricción de ancho arbitraria. Puede hacerlo todo en Interface Builder de la siguiente manera.

  • El ancho del botón intrínseco se deriva del ancho del título más el ancho del icono más las inserciones de los bordes de contenido izquierdo y derecho .

  • Si un botón tiene una imagen y un texto, están centrados como un grupo, sin relleno entre ellos.

  • Si agrega un contenido de contenido izquierdo, se calcula en relación con el texto, no con el icono de texto +.

  • Si establece un recuadro negativo en la imagen izquierda, la imagen se extrae hacia la izquierda pero el ancho general del botón no se ve afectado.

  • Si establece un recuadro negativo en la imagen izquierda, el diseño real usa la mitad de ese valor. Entonces, para obtener un recuadro izquierdo de -20 puntos, debe usar un valor de recuadro izquierdo de -40 puntos en Interface Builder.

Por lo tanto, proporciona una inserción de contenido izquierda lo suficientemente grande como para crear espacio para la inserción izquierda deseada y el relleno interno entre el icono y el texto, y luego desplaza el icono a la izquierda duplicando la cantidad de relleno que desea entre el icono y el texto. El resultado es un botón con insertos de contenido iguales a la izquierda y a la derecha, y un par de texto e íconos que se centran como un grupo, con una cantidad específica de relleno entre ellos.

Algunos valores de ejemplo:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
jaredsinclair
fuente
¿Por qué el diseño real utiliza la mitad del valor de inserción izquierdo negativo? ¡He encontrado el mismo problema!
Tony Lin
1
Es genial que haya una solución, pero espero que esto no se use para justificar el comportamiento extraño de UIButton.
funct7
205

Puede hacer que esto funcione en Interface Builder (sin escribir ningún código), mediante el uso de una combinación de títulos y contenidos negativos y positivos.

ingrese la descripción de la imagen aquí

Actualización : Xcode 7 tiene un error en el que no puede ingresar valores negativos en el Rightcampo Insertar, pero puede usar el control paso a paso al lado para disminuir el valor. (Gracias Stuart)

Hacer esto agregará 8 puntos de espacio entre la imagen y el título y aumentará el ancho intrínseco del botón en la misma cantidad. Me gusta esto:

ingrese la descripción de la imagen aquí

n.Drake
fuente
2
Está utilizando contentEdgeInsets (que no tiene errores) para permitir que el autolayout aumente el ancho del botón. Y mueva la etiqueta al espacio vacío a la derecha. Manera inteligente de solucionar el error de inserción del borde del título.
ugur
77
Este truco ya no funciona. El generador de interfaces ya no acepta valores negativos en el Rightcampo.
Joris Mans
77
@JorisMans No puede escribir valores negativos, pero funcionó para mí usando el control paso a paso a la derecha del campo de texto para bajar al valor negativo requerido ... ¡vaya!
Stuart
3
Esta debería ser la primera respuesta, ¿por qué está aquí abajo? He probado los otros 5 antes de encontrar esto ...
Lord Zsolt
2
Hice el derecho de contenido en el recuadro 16 para centrar el texto en UIButton
coolcool1994
96

¿Por qué no anular el intrinsicContentSizemétodo en UIView? Por ejemplo:

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

Esto debería indicarle al sistema de distribución automática que debería aumentar el tamaño del botón para permitir las inserciones y mostrar el texto completo. No estoy en mi propia computadora, así que no he probado esto.

Maarten
fuente
1
Los botones no deberían anularse hasta donde yo sé. El problema es que cada tipo de botón es implementado por una subclase diferente.
Sulthan
2
intrinsicContentSizees un método en UIView, no en UIButton, por lo que no estaría jugando con ningún método de UIButton. Apple no cree que sea un problema: "Anular este método permite que una vista personalizada comunique al sistema de diseño el tamaño que le gustaría tener en función de su contenido". Y el OP no dijo nada sobre diferentes botones, solo el uno.
Maarten
1
Esto definitivamente funciona y es la solución con la que fui. intrinsicContentSizede hecho, es un método en UIView y UIButton es una subclase de UIView, por lo que, por supuesto, puede anular este método; nada en los documentos de Apple dice que no deberías. Simplemente haga una subclase UIButton usando el método anulado de Maarten y cambie su UIButton en Interface Builder para que sea del tipo YourUIButtonSubclass y funcionará perfectamente.
n8tr
37
Me parece que intrinsicContentSizeUIButton debería agregar el título EdgeInsets, voy a presentar un error con Apple.
programa
66
Estoy de acuerdo, y lo mismo para imageEdgeInsets.
Ricardo Sanchez-Saez
87

No ha especificado cómo está configurando las inserciones, por lo que supongo que está usando titleEdgeInsets porque veo el mismo efecto que está obteniendo. Si uso contentEdgeInsets en su lugar, funciona correctamente.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
rdelmar
fuente
De hecho, estoy usando titleEdgeInsets. Necesito distanciar el título de la imagen, no la imagen desde el borde del botón. ¿Quizás debería usar una imagen con algo de relleno? Aunque parece hacky.
Ben Packard
Esto funciona perfectamente en combinación con la distribución automática, ¡gracias!
Cal S
3
Esta es la mejor solución, ya que hace exactamente lo que desea sin tocar intrinsicContentSize.
RyJ
28
¡Esto NO responde la pregunta cuando se usa una imagen y se necesita ajustar el recuadro entre la imagen y el título!
Brody Robertson el
23

Y para Swift trabajó esto:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

Love U Swift

clavija
fuente
1
Aunque se supone que no debe hacerlo, es mejor subclasificar en este caso porque los documentos de Apple establecen explícitamente que el tamaño intrínseco no incluye titleEdgeInsets en su cálculo y, por lo tanto, al usar una extensión está violando no solo las expectativas de Apple, sino todos los demás desarrolladores que leen los documentos
Sirenas
18

Este hilo es un poco viejo, pero me encontré con esto y pude resolverlo usando un recuadro negativo. Por ejemplo, sustituya los valores de relleno deseados aquí:

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

¡Combinado con las restricciones correctas de auto-distribución, terminas con un botón de redimensionamiento automático que contiene una imagen y texto! Visto a continuación con el desiredLeftPaddingconjunto a 10.

Botón con imagen y texto corto

Botón con imagen y texto largo

Puede ver que el marco real del botón no abarca la etiqueta (ya que la etiqueta se desplaza 10 puntos a la derecha, fuera de los límites), pero hemos logrado 10 puntos de relleno entre el texto y la imagen.

Brian Gerstle
fuente
1
Esta es la solución que he usado, ya que no requiere subclases. No funcionará si su botón tiene un fondo, pero eso no suele ser un problema con iOS 7
José Manuel Sánchez
Esto funcionará con una imagen de fondo si también configura el desplazamiento del contenido del botón (valor positivo> = recuadro).
Ben Flynn
9

Para Swift 3 basado en la respuesta de pegpeg :

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}
TheoK
fuente
Hola. Quiero usar el botón de extensión personalizado en el creador de interfaces. ayuda por
favor
6

Todo lo anterior no funcionó para iOS 9+ , lo que hice es:

  • Agregue una restricción de ancho (para un ancho mínimo cuando el botón no tiene texto. El botón se escalará automáticamente si se proporciona texto)
  • establecer la relación a mayor que o igual

ingrese la descripción de la imagen aquí

Ahora para agregar un borde alrededor del botón solo use el método:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
Oritm
fuente
Por qué no? Se escala automáticamente con el contenido, solo tiene que establecer un ancho mínimo (que puede ser más pequeño que el texto que se mostrará)
Oritm
Porque define un ancho mínimo. La idea completa de autolayout es hacerlo sin establecer ningún ancho explícito (mínimo).
Joris Mans
No se trata del ancho, puede establecer el ancho en 1 si lo prefiere, pero la distribución automática debe saber que el ancho puede ser igual o mayor .
Actualicé
No necesita la restricción de ancho en absoluto, contentEdgeInset es la clave, el diseño automático luego lo usa para el tamaño de contenido intrínseco.
Chris Conover
5

Quería agregar un espacio de 5 puntos entre mi ícono UIButton y la etiqueta. Así es como lo logré:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

La forma en que contentEdgeInsets, titleEdgeInsets e imageEdgeInsets se relacionan entre sí requiere un poco de toma y daca de cada inserción. Entonces, si agrega algunas inserciones a la izquierda del título, debe agregar una inserción negativa a la derecha y proporcionar más espacio (a través de una inserción positiva) en el contenido a la derecha.

Al agregar un recuadro de contenido adecuado para que coincida con el cambio de las inserciones de título, mi texto no sale de los límites del botón.

orj
fuente
3

La opción también está disponible en el generador de interfaces. Ver el recuadro. Puse izquierda y derecha en 3. Funciona como un encanto.

Captura de pantalla del generador de interfaz

zeiteisen
fuente
1
Sí, como explica esta respuesta , la razón por la que funciona es porque estás ajustando Edge: Contenido aquí en lugar de Edge: Título o Edge: Imagen .
smileyborg
1

La solución que uso es agregar una restricción de ancho en el botón. Luego, en algún lugar de la inicialización, después de configurar el texto, actualice la restricción de ancho de esta manera:

self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;

Donde 8 es cualquiera que sea tu inserción.

Bob Spryn
fuente
¿Qué es buttonWidthConstraint?
Alexey Golikov
@AlexeyGolikov An NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
1
Esta no es una gran solución, porque si el tamaño del contenido intrínseco del botón cambia, necesitaría actualizar manualmente constantla restricción al nuevo valor ... y saber cuándo cambia el tamaño del contenido intrínseco del botón es difícil sin subclasificar el botón.
smileyborg
Ayup Ya no uso este método. Sorprendido fue digno de un voto negativo pero ¯_ (ツ) _ / ¯
Bob Spryn
Se setNeedsUpdateConstraintspuede realizar una llamada a "manualmente" después de actualizar el título o la imagen del botón. A continuación, puede anular updateConstraintsy volver a calcular buttonWidthConstraintla constante de allí. Este no es necesariamente el mejor enfoque, pero funciona lo suficientemente bien para mí. YMMV;)
Olivier
1

llamar a sizeToFit () se asegura de que contentEdgeInset tenga efecto

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
Ali
fuente