UITableViewCell con margen izquierdo de autolayout diferente en iPhone y iPad

Estoy utilizando un UITableView agrupado con celdas estáticas para una pantalla / escena de opciones. Todo se hace en Xcode 6.1 / iOS 8.1.x / Storyboard usando Autolayout. Dentro de los grupos de tablas hay tipos mixtos de celdas y hay dos tipos que me causan problemas:

  1. Células con estilo personalizado y
  2. Células con estilo “Detalle correcto”

En la celda n. ° 1, puedo establecer una restricción para el margen izquierdo entre la etiqueta y el contenedor principal. En la celda n. ° 2 no puedo establecer ninguna restricción en Interface Builder hasta donde yo sé. Establecí el margen izquierdo en la etiqueta en la celda n. ° 1 para que se alinee con la etiqueta en la celda n. ° 2. Todo se ve bien en un iPhone, pero si muestro la misma tabla en un iPad donde el tamaño del contenedor de la vista de tabla es la mitad del tamaño de pantalla, la celda n. ° 2 obtiene más margen (¿dinámicamente?) Mientras que la celda n. ° 1 establecer en las restricciones. También traté de cambiar el margen izquierdo en la celda n. ° 1 con el atributo “relativo al margen” pero fue en vano.

iPhone:

Tabla en iPhone

iPad (con ancho de vista de tabla = 1/2 tamaño de pantalla)

Tabla en iPad

Entonces, la pregunta es: ¿cómo establezco las restricciones para la etiqueta en la celda n. ° 1 para que se alinee como la celda n. ° 2?

Aquí también hay un enlace a un proyecto de ejemplo de Xcode 6.1 que demuestra el problema. Ejecute en iPhone y iPad para ver la diferencia:

https://dl.dropboxusercontent.com/u/5252156/Code/tableViewTest.zip

Esta pregunta podría estar relacionada con la celda de tabla estática de Layout para iPhone y iPad , pero también podría ser diferente para iOS 8, ya que ahora se supone que todo es adaptativo. Es por eso que decidí publicar esta pregunta de todos modos.

Como arreglarlo

Después de luchar con el equipo de informes de errores de Apple con muchos ejemplos de proyectos y capturas de pantalla y analizar esa respuesta , descubrí que la solución para que sus células personalizadas se comporten consistentemente con respecto a sus márgenes y sean como las UITableViewCells predeterminadas, tiene que hacer lo siguiente (principalmente basado en la respuesta de Becky , destaqué lo que es diferente y lo que hizo que funcionara para mí):

  1. Seleccione la vista de contenido de su celda en IB
  2. Ir al Inspector de tallas
  3. En la sección Márgenes de diseño, marque Conservar márgenes de Superview (no haga clic en el signo más)

    Comprobando preservar márgenes de superview

  4. (Y aquí está la clave) Haz lo mismo para la celda en sí (el padre de la vista de contenido si lo deseas)

    La celda y no el contenido se ven esta vez

  5. Configure sus restricciones de la siguiente manera: Label.Leading = Superview.Línea de Margen (con una constante de 0)

    Establecer la restricción para la etiqueta de la celda personalizada

¡Ahora todas sus celdas tendrán su etiqueta consistente con las celdas predeterminadas! Esto funciona para mí en Xcode 7 y posteriores e incluye la corrección mencionada en el hilo al que me refería. IB y el simulador ahora deberían mostrar tags alineadas correctamente.

Resultado final en el simulador

También podría hacer algo de esto programáticamente, por ejemplo en la clase View Controller:

 cell.preservesSuperviewLayoutMargins = true cell.contentView.preservesSuperviewLayoutMargins = true 

O puede configurarlo llamando a UIAppearance una vez al inicio (solo lo sé Swift, lo siento):

 UITableViewCell.appearance().preservesSuperviewLayoutMargins = true UITableViewCell.appearance().contentView.preservesSuperviewLayoutMargins = true 

Cómo y por qué funciona

Como Ethan amablemente señaló, la propia documentación de Apple en UIView describe preservesSuperviewLayoutMargins siguiente manera:

Cuando el valor de esta propiedad es true , los márgenes de la supervista también se consideran al diseñar el contenido. Este margen afecta a los diseños donde la distancia entre el borde de una vista y su supervista es menor que el margen correspondiente. Por ejemplo, puede tener una vista de contenido cuyo marco coincida exactamente con los límites de su supervista. Cuando cualquiera de los márgenes de la supervista está dentro del área representada por la vista de contenido y sus propios márgenes, UIKit ajusta el diseño de la vista de contenido para respetar los márgenes de la supervista. El monto del ajuste es la cantidad más pequeña que se necesita para garantizar que el contenido también se encuentre dentro de los márgenes de la supervista.

Por lo tanto, si desea que el contenido de su celda se alinee con los márgenes de TableView (es bisabuelo), debe tener los dos elementos ascendentes de contenido, la Vista de contenido y la Celda de tabla, preservar los márgenes de su propia supervista.

Por qué esto no es el comportamiento predeterminado me sorprende: siento que la mayoría de los desarrolladores que no desean personalizar todo esperarían esta “herencia” por defecto.

Corrí en el mismo problema que tú y se me ocurrió una solución.

En primer lugar, un pequeño trasfondo: desde iOS 8, las celdas predeterminadas de la vista de tabla respetan el layoutMargins la layoutMargins para adaptarse a diferentes características (también conocidas como dispositivos aka). Por ejemplo, los márgenes de diseño en todos los iPhones (excepto iPhone 6 Plus cuando se muestra en una hoja de formulario) son {8, 16, 8, 16} . En iPad son {8, 20, 8, 20} . Entonces ahora sabemos que hay 4 píxeles de diferencia, lo que probablemente no respete su celda de vista de tabla personalizada.

Su subclase de celda de vista de tabla necesita adaptar la restricción del margen izquierdo cuando los cambios en layoutMargins.

Aquí está el fragmento de código relevante:

  - (void)layoutMarginsDidChange { [super layoutMarginsDidChange]; self.leftLayoutMarginConstraint.constant = self.layoutMargins.left; self.rightLayoutMarginConstraint.constant = self.layoutMargins.right; } 

La adaptación a los márgenes de diseño en el código le permite obtener siempre el relleno adecuado para su etiqueta de título.

También puede echar un vistazo a una de mis subclases UITableViewCell que ya respetan layoutMargins: https://github.com/bhr/BHRExtensions/blob/master/BHRExtensions/Utilities/BHRTitleAndValueTableCell.m

Aclamaciones

Después de leer las respuestas existentes y no encontrar una solución programática obvia, investigué un poco más y ahora tengo una buena respuesta para cualquier otra persona que enfrenta este problema.

En primer lugar, no es necesario establecer preservesSuperviewLayoutMargins en la vista de la celda o de contenido, como implican otras respuestas . Si bien el valor predeterminado es false , al cambiarlo a true no tuve un efecto notable que pudiera ver.

La clave para hacer que esto realmente funcione es la propiedad UIView en UIView . Usando este valor, podemos simplemente fijar fácilmente el leadingAnchor de cualquier subvista al leadingAnchor de la guía. Así es como se ve en el código (y bien puede ser lo que IB está haciendo detrás de las escenas como en la respuesta de Jonas ).

En una subclase UITableViewCell , harías algo como esto:

 override func updateConstraints() { let margins = contentView.layoutMarginsGuide let leading = margins.leadingAnchor subview1.leadingAnchor.constraintEqualToAnchor(leading).active = true subview2.leadingAnchor.constraintEqualToAnchor(leading).active = true super.updateConstraints() } 

Actualización de Swift 4.1

 override func updateConstraints() { let margins = contentView.layoutMarginsGuide let leading = margins.leadingAnchor subview1.leadingAnchor.constraint(equalTo: leading).isActive = true subview2.leadingAnchor.constraint(equalTo: leading).isActive = true super.updateConstraints() } 

¡Eso es todo! Si está desarrollando versiones para iOS anteriores a iOS 9, deberá sustituir los anclajes de diseño y usar el recuadro de layoutMargins lugar.


Nota: escribí una biblioteca para hacer que el ancla sea un poco más bonita, si prefieres una syntax más limpia. Se llama SuperLayout y está disponible en Cocoapods. En la parte superior de su archivo fuente, importe SuperLayout :

 import SuperLayout 

Y luego, en su bloque de diseño, use ~~ , ≤≤ , y ≥≥ para fijar restricciones:

 override func updateConstraints() { let margins = contentView.layoutMarginsGuide subview1.leadingAnchor ~~ margins.leadingAnchor subview2.leadingAnchor ~~ margins.leadingAnchor super.updateConstraints() } 

Pude obtener celdas con estilos personalizados alineados con las celdas estándar haciendo lo siguiente:

  1. En el contorno del documento, seleccione la “Vista de contenido” para la celda con el estilo personalizado.
  2. Ve al Inspector de tallas.
  3. En el menú desplegable “Márgenes de diseño”, presiona el pequeño símbolo más junto a “Conservar márgenes de Superview”.
  4. Seleccione la clase de tamaño iPad, que es “Ancho regular x Altura regular”.
  5. Marque la checkbox junto a “Conservar márgenes de Superview”.
  6. Resuelva las advertencias de diseño automático actualizando los marcos.

Esto funcionó para mí en Xcode 7; Espero que funcione en Xcode 6 también.

Tuve este problema al probar en un iPad Air, OS 10.1.1. Los encabezados de la tabla fueron sangrados mucho más allá de lo que deberían haber sido, y fue aún peor en orientación horizontal. Pero estaban bien en iPhones hasta OS 11.

La sorprendente solución fue la siguiente línea de código, justo después de que se creó la tabla (lo siento, solo trabajo en C #, pero es fácil calcular los equivalentes de Obj-C y Swift):

 myTableView.SeparatorInset = myTableView.SeparatorInset; 

¡Entonces todo estaba sangrado como debería ser!