Espacie uniformemente múltiples vistas dentro de una vista de contenedor

279

El diseño automático me dificulta la vida. En teoría, iba a ser realmente útil cuando me cambiara, pero parece que lucho todo el tiempo.

Hice un proyecto de demostración para tratar de encontrar ayuda. ¿Alguien sabe cómo hacer que los espacios entre las vistas aumenten o disminuyan de manera uniforme cada vez que se cambie el tamaño de la vista?

Aquí hay tres etiquetas (manualmente espaciadas verticalmente, incluso):

imagen1

Lo que quiero es que cambien el tamaño de su espacio (no el tamaño de la vista) de manera uniforme cuando gire. Por defecto, las vistas superior e inferior se desplazan hacia el centro:

imagen2

nothappybob
fuente
quizás sea útil establecer una restricción de una etiqueta a otra, y establecer una relación "mayor que o igual" será útil
BergP
programáticamente, por ejemplo, con el uso de este método de NSLayoutConstraint: + (id) restrictintWithItem: (id) view1 atributo: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) relación con Item: (id) view2 atributo: (NSLayoutAttribute) attr2 multiplicador: (CGFloat) multiplier: (CGFloat) multiplo constante: (CGFloat) c
BergP
Abrí un código genérico de reemplazo UITabBar que generaliza este enfoque usando el diseño automático. Puede consultar las pruebas para ver cómo genero mis restricciones de diseño automático. GGTabBar
Goles

Respuestas:

211

Entonces mi enfoque le permite hacer esto en el generador de interfaces. Lo que debe hacer es crear 'vistas espaciadoras' que haya configurado para igualar las alturas por igual. Luego agregue restricciones superiores e inferiores a las etiquetas (vea la captura de pantalla).

ingrese la descripción de la imagen aquí

Más específicamente, tengo una restricción superior en 'Vista espaciadora 1' para supervisar con una restricción de altura de menor prioridad que 1000 y con Altura igual a todas las otras 'vistas espaciadoras'. 'Spacer View 4' tiene una restricción de espacio inferior para la supervista. Cada etiqueta tiene sus respectivas restricciones superior e inferior a sus "vistas espaciadoras" más cercanas.

Nota: Asegúrese de NO tener restricciones adicionales de espacio superior / inferior en sus etiquetas para supervisar; solo los de las 'vistas espaciales'. Esto será satisfactorio ya que las restricciones superior e inferior están en 'Space View 1' y 'Spacer View 4' respectivamente.

Duh 1: Dupliqué mi vista y simplemente la puse en modo horizontal para que pudieras ver que funcionaba.

Duh 2: Las 'vistas espaciadoras' podrían haber sido transparentes.

Duh 3: este enfoque podría aplicarse horizontalmente.

Spencer Hall
fuente
10
Esto funciona. De hecho, las vistas espaciadoras se pueden ocultar y seguirán produciendo el diseño correcto.
paulmelnikow
Esto fue súper útil. Como fue el comentario sobre solo marcar las vistas espaciadoras como ocultas. Entonces todavía puede verlos en su storyboard / xib, pero no aparecen en su aplicación. Muy agradable.
shulmey
Lo conseguí casi trabajando después de varias horas de intentos repetidos. Desafortunadamente, Xcode sigue eliminando mis restricciones de espacio superior a botón y espacio inferior, rompiendo la cadena entre botones y espaciadores, y reemplazándolas por restricciones arbitrarias de espacio superior a supervista que son inútiles.
Desty
El mismo enfoque funciona para mí en diseño horizontal: tengo vistas de ancho fijo separadas por vistas espaciadoras de igual ancho. Es genial que no necesite mezclarlo con ningún código. ¡Gracias!
Kamil Nomtek.com
2
No es necesario usar espaciadores. Vea mi respuesta a continuación.
smileBot
316

¡MIRA, SIN ESPACIADORES!

Según las sugerencias en la sección de comentarios de mi respuesta original, especialmente las sugerencias útiles de @ Rivera, he simplificado mi respuesta original.

Estoy usando gifs para ilustrar cuán simple es esto. Espero que encuentres los gifs útiles. En caso de que tenga un problema con los gifs, he incluido la respuesta anterior a continuación con capturas de pantalla simples.

Instrucciones:

1) Agregue sus botones o etiquetas. Estoy usando 3 botones.

2) Agregue una restricción centro x de cada botón a la supervista:

ingrese la descripción de la imagen aquí

3) Agregue una restricción de cada botón a la restricción de diseño inferior:

ingrese la descripción de la imagen aquí

4) Ajuste la restricción agregada en el # 3 anterior de la siguiente manera:

a) seleccione la restricción, b) elimine la constante (establecida en 0), c) cambie el multiplicador de la siguiente manera: tome el número de botones + 1, y comience en la parte superior, configure el multiplicador como buttonCountPlus1: 1 , y luego buttonCountPlus1 : 2 , y finalmente buttonCountPlus1: 3 . (Explico de dónde obtuve esta fórmula en la respuesta anterior a continuación, si está interesado).

ingrese la descripción de la imagen aquí

5) ¡ Aquí hay una demo en ejecución!

ingrese la descripción de la imagen aquí

Nota: Si sus botones tienen alturas más grandes, deberá compensar esto en el valor constante ya que la restricción es desde la parte inferior del botón.


Vieja respuesta


A pesar de lo que dicen los documentos de Apple y el excelente libro de Erica Sadun ( Auto Layout Demystified ), es posible espaciar uniformemente las vistas sin espaciadores. Esto es muy simple de hacer en IB y en código para cualquier número de elementos que desee espaciar de manera uniforme. Todo lo que necesita es una fórmula matemática llamada "fórmula de sección". Es más sencillo de hacer que de explicar. Haré lo mejor que pueda al demostrarlo en IB, pero es igual de fácil hacerlo en código.

En el ejemplo en cuestión, usted

1) comience configurando cada etiqueta para que tenga una restricción central. Esto es muy sencillo de hacer. Simplemente controle el arrastre desde cada etiqueta hasta la parte inferior.

2) Mantenga presionada la tecla Mayús, ya que también podría agregar la otra restricción que vamos a utilizar, a saber, la "guía de diseño de espacio inferior a inferior".

3) Seleccione la "guía de diseño de espacio inferior a inferior" y "centrar horizontalmente en el contenedor". Haga esto para las 3 etiquetas.

Mantenga presionada la tecla Mayús para agregar estas dos restricciones para cada etiqueta

Básicamente, si tomamos la etiqueta cuya coordenada deseamos determinar y la dividimos por el número total de etiquetas más 1, entonces tenemos un número que podemos agregar a IB para obtener la ubicación dinámica. Estoy simplificando la fórmula, pero podría usarla para establecer el espaciado horizontal o tanto vertical como horizontal al mismo tiempo. ¡Es súper poderoso!

Aquí están nuestros multiplicadores.

Etiqueta1 = 1/4 = .25,

Etiqueta2 = 2/4 = .5,

Etiqueta3 = 3/4 = .75

(Editar: @Rivera comentó que simplemente puede usar las proporciones directamente en el campo multiplicador, ¡y xCode con hacer los cálculos!)

4) Entonces, seleccionemos Label1 y seleccione la restricción inferior. Me gusta esto: ingrese la descripción de la imagen aquí

5) Seleccione el "Segundo elemento" en el Inspector de atributos.

6) En el menú desplegable, seleccione "Invertir primer y segundo elemento".

7) Ponga a cero la constante y el valor de wC hAny. (Puede agregar un desplazamiento aquí si lo necesita).

8) Esta es la parte crítica: en el campo multiplicador agregue nuestro primer multiplicador 0.25.

9) Mientras está en él, configure el "primer elemento" superior en "CenterY" ya que queremos centrarlo en el centro y de la etiqueta. Así es como debería verse todo eso.

ingrese la descripción de la imagen aquí

10) Repita este proceso para cada etiqueta y conecte el multiplicador relevante: 0.5 para Label2 y 0.75 para Label3. ¡Aquí está el producto final en todas las orientaciones con todos los dispositivos compactos! Súper simple He estado buscando muchas soluciones que involucran resmas de código y espaciadores. Esta es de lejos la mejor solución que he visto sobre el tema.

Actualización: @kraftydevil agrega que la guía de diseño inferior solo aparece en los guiones gráficos, no en xibs. Use 'Espacio inferior al contenedor' en xibs. ¡Buena atrapada!

ingrese la descripción de la imagen aquí

smileBot
fuente
2
Todavía estoy luchando con eso. Creo que debe usar Xcode 6 porque no hay una opción 'wC hAny' en las restricciones de Xcode 5. Cuando sigo sus instrucciones, todas las subvistas se quedan pegadas en el centro superior.
kraftydevil
44
@kraftydevil La guía de diseño inferior AFAIK solo aparece en guiones gráficos, no en xibs. Use 'Espacio inferior al contenedor' en xibs.
Timur Kuchkarov
66
Ah, también vale la pena señalar que IB en Xcode 6 admite multiplicadores de relación, por lo que puede incluir cosas como "1: 4", "2: 5", "3: 4", etc.
Benjohn
3
Tratando de descubrir cómo hacer esto horizontalmente, como el primer TextView 1/3 del camino desde la izquierda, y un segundo TextView 2/3 (resto del camino) ... ... (EDITAR) lo obtuve: haga el primer 1/3 (izquierdo) Espacio
posterior
3
Para el espaciado horizontal, simplemente establezca restricciones finales a la supervista para cada subvista, y luego configure el multiplicador de relación, por ejemplo. 5: 1, 5: 2, 5: 3, etc. moviéndose de izquierda a derecha.
user3344977
98

Solución muy rápida de Interface Builder:

Para que cualquier número de vistas esté espaciado uniformemente dentro de una supervista, simplemente asigne a cada una una restricción "Alinear centro X a supervista" para diseño horizontal, o "Alinear centro Y supervista" para diseño vertical, y configure el multiplicador para que sea N:p( NOTA: algunos he tenido mejor suerte con p:N- ver más abajo )

dónde

N = total number of viewsy

p = position of the view including spaces

La primera posición es 1, luego un espacio, haciendo la siguiente posición 3, por lo que p se convierte en una serie [1,3,5,7,9, ...]. Funciona para cualquier cantidad de vistas.

Entonces, si tiene 3 vistas para espaciar, se ve así:

Ilustración de cómo distribuir uniformemente las vistas en IB

EDITAR Nota: La elección de N:po p:Ndepende del orden de relación de su restricción de alineación. Si "Primer elemento" es Superview.Center, puede usarlo p:N, mientras que si Superview.Center es "Segundo elemento", puede usarlo N:p. En caso de duda, pruebe ambos ... :-)

Asignar
fuente
Esta solución no reflejará el diseño si cambia a otro idioma
wod
@wod ¿Qué quieres decir? ¿Cambiar qué a otro idioma? ¿Y qué se rompe exactamente?
Mete
44
¿Qué pasa si los elementos no son del mismo tamaño? este patrón no funcionará correctamente ¿verdad?
ucangetit
2
No entiendo cómo esta solución es correcta? ¿El espacio entre el borde de la pantalla y los elementos exteriores es diferente al espacio entre los elementos exteriores y el elemento central?
myles
66
Esto no fue para mí "como está". Tuve que invertir los multiplicadores (ej. El 3: 1 del primer elemento se convierte a 1: 3). Solo tirar esto por ahí si ayuda a alguien.
Ces
70

A partir de iOS 9, Apple ha hecho esto muy fácil con el (tan esperado) UIStackView. Simplemente seleccione las vistas que desea contener en el Creador de interfaces y elija Editor -> Incrustar en -> Vista de pila. Establezca las restricciones de ancho / alto / margen apropiadas para la vista de pila, y asegúrese de establecer la propiedad Distribución en 'Espaciado igual':

Por supuesto, si necesita admitir iOS 8 o inferior, tendrá que elegir una de las otras opciones.

Glorfindel
fuente
Sí, es triste que esto no tenga compatibilidad retro, así que hasta que su aplicación necesite soporte para IOS8 @Mete, la respuesta es la mejor por ahora.
ZiggyST
51

He estado en una montaña rusa de amar autolayout y odiarlo. La clave para amarlo parece ser aceptar lo siguiente:

  1. La edición del constructor de interfaces y la creación automática de restricciones "útiles" es casi inútil para todos, excepto el caso más trivial
  2. Crear categorías para simplificar las operaciones comunes es un salvavidas ya que el código es tan repetitivo y detallado.

Dicho esto, lo que está intentando no es sencillo y sería difícil de lograr en el generador de interfaces. Es bastante simple de hacer en código. Este código, en viewDidLoad, crea y coloca tres etiquetas de cómo las solicita:

// Create three labels, turning off the default constraints applied to views created in code
UILabel *label1 = [UILabel new];
label1.translatesAutoresizingMaskIntoConstraints = NO;
label1.text = @"Label 1";

UILabel *label2 = [UILabel new];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label 2";

UILabel *label3 = [UILabel new];
label3.translatesAutoresizingMaskIntoConstraints = NO;
label3.text = @"Label 3";

// Add them all to the view
[self.view addSubview:label1];
[self.view addSubview:label2];
[self.view addSubview:label3];

// Center them all horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

// Center the middle one vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

// Position the top one half way up
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:0.5 constant:0]];

// Position the bottom one half way down
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:1.5 constant:0]];

Como digo, este código se simplifica mucho con un par de métodos de categoría UIView, pero para mayor claridad, lo he hecho hasta aquí.

La categoría está aquí para aquellos interesados, y tiene un método para espaciar uniformemente una variedad de vistas a lo largo de un eje particular.

jrturton
fuente
Usted señor, es un salvavidas. No entendí el # 1 hasta ahora. El creador de interfaces me está volviendo loco, pero pensé que podía hacer las mismas cosas. Prefiero usar el código de todos modos, así que esto es perfecto, y voy directamente a los métodos de categoría.
nothappybob
Aunque definitivamente es bastante cercano, esto no resuelve el problema exacto (manteniendo el tamaño de la vista igual, incluso con un espacio entre subvistas). Esta solución es la respuesta refinada del caso general.
smileyborg
21

La mayoría de estas soluciones dependen de que haya un número impar de elementos para que pueda tomar el elemento central y centrarlo. ¿Qué sucede si tiene un número par de elementos que aún desea distribuir de manera uniforme? Aquí hay una solución más general. Esta categoría distribuirá uniformemente cualquier número de elementos a lo largo del eje vertical u horizontal.

Ejemplo de uso para distribuir verticalmente 4 etiquetas dentro de su supervista:

[self.view addConstraints:
     [NSLayoutConstraint constraintsForEvenDistributionOfItems:@[label1, label2, label3, label4]
                                        relativeToCenterOfItem:self.view
                                                    vertically:YES]];

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of views to be evenly distributed horizontally
 * or vertically relative to the center of another item. This is used to maintain an even
 * distribution of subviews even when the superview is resized.
 */
+ (NSArray *) constraintsForEvenDistributionOfItems:(NSArray *)views
                             relativeToCenterOfItem:(id)toView
                                         vertically:(BOOL)vertically;

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = (2*i + 2) / (CGFloat)([views count] + 1);
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

@end
Ben Dolman
fuente
1
Funcionó eliminando todas las restricciones e inmediatamente después, agregando las restricciones de Ancho y Altura a esos objetos, luego llamando al método restrictintsForEvenDistributionOfItems: relativeToCenterOfItem: verticalmente:
carlos_ms
@carlos_ms Me encantaría ver tu código, porque cuando intento con un número par de elementos, este código funciona perfectamente al cambiar las orientaciones del dispositivo. ¿Está seguro de que, sin darse cuenta, no tiene otras restricciones (como las restricciones de tamaño automático) que están causando un conflicto?
Ben Dolman el
Aquí hay un ejemplo de cómo crear 4 etiquetas y espaciarlas uniformemente a lo largo del eje vertical: gist.github.com/bdolman/5379465
Ben Dolman el
20

La forma correcta y más fácil es usar Stack Views.

  1. Agregue sus etiquetas / vistas a la Vista de pila :

ingrese la descripción de la imagen aquí

  1. Seleccione la Vista de pila y configure la Distribución para que sea Espaciado igual :

ingrese la descripción de la imagen aquí

  1. Agregue espacio a las restricciones vecinas más cercanas a la vista de pila y actualice los marcos:

ingrese la descripción de la imagen aquí

  1. Agregue restricciones de altura a todas las etiquetas (opcional). Necesario solo para vistas que no tienen Tamaño intrínseco). Las etiquetas, por ejemplo, no necesitan aquí restricciones de altura y solo necesitan establecer numberOfLines = 3 o 0, por ejemplo.

ingrese la descripción de la imagen aquí

  1. Disfruta de la vista previa:

ingrese la descripción de la imagen aquí

Oleh Kudinov
fuente
2
Esto es> = iOS 9.0 solo así que verifique su objetivo de implementación antes de comenzar a implementar :) ¡Genial ver que esto se ha agregado!
DivideByZer0
1
2018 ahora, use la vista de pila
Jingshao Chen
17

Echa un vistazo a la biblioteca de código abierto PureLayout . Ofrece algunos métodos API para distribuir vistas, incluidas variantes en las que el espacio entre cada vista es fijo (el tamaño de la vista varía según sea necesario) y donde el tamaño de cada vista es fijo (el espacio entre las vistas varía según sea necesario). Tenga en cuenta que todo esto se logra sin el uso de "vistas espaciadoras".

De NSArray + PureLayout.h :

// NSArray+PureLayout.h

// ...

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                         withFixedSpacing:(CGFloat)spacing;

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                            withFixedSize:(CGFloat)size;

// ...

Como todo es de código abierto, si está interesado en ver cómo se logra esto sin vistas espaciadoras, simplemente eche un vistazo a la implementación. (Depende de apalancamiento tanto el constant y multiplier para las restricciones.)

smileyborg
fuente
¡Buen trabajo! Aunque probablemente debería cambiar el nombre del repositorio para que esté disponible a través de cocoapods.
jrturton
9

Tengo un problema similar y descubrí esta publicación. Sin embargo, ninguna de las respuestas proporcionadas actualmente resuelve el problema de la manera que desea. No hacen el espaciado por igual, sino que distribuyen el centro de las etiquetas por igual. Es importante entender que esto no es lo mismo. He construido un pequeño diagrama para ilustrar esto.

Ver ilustración de espaciado

Hay 3 vistas, todas de 20pt de altura. El uso de cualquiera de los métodos sugeridos distribuye equitativamente los centros de las vistas y le brinda el diseño ilustrado. Observe que el centro y de las vistas está espaciado por igual. Sin embargo, el espacio entre la supervista y la vista superior es de 15 puntos, mientras que el espacio entre las subvistas es de solo 5 puntos. Para tener las vistas espaciadas por igual, ambas deben ser de 10 puntos, es decir, todas las flechas azules deben tener 10 puntos.

Sin embargo, todavía no he encontrado una buena solución genérica. Actualmente, mi mejor idea es insertar "vistas de espaciado" entre las subvistas y configurar las alturas de las vistas de espaciado para que sean iguales.

Florian
fuente
1
He usado la solución de vistas espaciadoras ocultas, que funciona bien.
paulmelnikow
8

Pude resolver esto completamente en IB:

  1. Haga restricciones para alinear el centro Y de cada una de sus subvistas con el borde inferior de la supervista.
  2. Establezca el multiplicador de cada una de estas restricciones en 1 / 2n, 3 / 2n, 5 / 2n, ..., n-1 / 2n donde n es el número de subvistas que está distribuyendo.

Entonces, si tiene tres etiquetas, establezca los multiplicadores para cada una de esas restricciones en 0.1666667, 0.5, 0.833333.

hayesk
fuente
1
De hecho, tuve que hacer 1 / n 3 / n 5 / n 7 / n hasta (2n-1) / n
Hamzah Malik
5

Encontré un método perfecto y simple. El diseño automático no le permite cambiar el tamaño de los espacios por igual, pero sí le permite cambiar el tamaño de las vistas por igual. Simplemente coloque algunas vistas invisibles entre sus campos y dígale al diseño automático que mantenga el mismo tamaño. Funciona perfectamente!

XIB inicial

XIB estirado

Una cosa de nota sin embargo; cuando reduje el tamaño en el diseñador de la interfaz, a veces se confundía y dejaba una etiqueta donde estaba, y tenía un conflicto si el tamaño se cambiaba en una cantidad extraña. De lo contrario, funcionó perfectamente.

editar: descubrí que el conflicto se convirtió en un problema. Debido a eso, tomé una de las restricciones de espacio, la eliminé y la reemplacé por dos restricciones, una mayor que o igual y una menor que o igual. Ambos tenían el mismo tamaño y tenían una prioridad mucho menor que las otras restricciones. El resultado no fue más conflicto.

Owen Godfrey
fuente
4

Sobre la base de la respuesta de Ben Dolman, esto distribuye las vistas de manera más uniforme (con relleno, etc.):

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    CGFloat min = 0.25;
    CGFloat max = 1.75;
    CGFloat d = (max-min) / ([views count] - 1);
    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = i * d + min;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}
Rayo W
fuente
3

Con etiquetas esto funciona bien al menos:

@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|

Si el primero tiene el mismo ancho que el segundo, y el segundo el tercero, y el tercero el primero, entonces todos obtendrán el mismo ancho ... Puede hacerlo tanto horizontalmente (H) como verticalmente (V).

Johannes
fuente
3

versión swift 3

let _redView = UIView()
        _redView.backgroundColor = UIColor.red
        _redView.translatesAutoresizingMaskIntoConstraints = false

        let _yellowView = UIView()
        _yellowView.backgroundColor = UIColor.yellow
        _yellowView.translatesAutoresizingMaskIntoConstraints = false

        let _blueView = UIView()
        _blueView.backgroundColor = UIColor.blue
        _blueView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(_redView)
        self.view.addSubview(_yellowView)
        self.view.addSubview(_blueView)

        var views = ["_redView": _redView, "_yellowView": _yellowView, "_blueView":_blueView]

        var nslayoutConstraint_H = NSLayoutConstraint.constraints(withVisualFormat: "|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_H)

        var nslayoutConstraint_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[_redView(60)]", options: NSLayoutFormatOptions.init(rawValue: 0), metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_V)


        let constraint_red = NSLayoutConstraint.init(item: self.view, attribute: .centerY, relatedBy: .equal, toItem: _redView, attribute: .centerY, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_red)

        let constraint_yellow = NSLayoutConstraint.init(item: self.view, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .centerX, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_yellow)

        let constraint_yellow1 = NSLayoutConstraint.init(item: _redView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 0.5, constant: 0)
        self.view.addConstraint(constraint_yellow1)

        let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
        self.view.addConstraint(constraint_yellow2)
Sarath Raveendran
fuente
1
Elaborar sobre cómo este código responde las preguntas ayudaría a futuros visitantes.
JAL
2

Sé que ha pasado un tiempo desde la primera respuesta, pero me encontré con el mismo problema y quiero compartir mi solución. Para las generaciones venideras ...

Configuré mis puntos de vista en viewDidLoad:

- (void)viewDidLoad {

    [super viewDidLoad];

    cancelButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
    [cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
    [self.view addSubview:cancelButton];

    middleButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    middleButton.translatesAutoresizingMaskIntoConstraints = NO;
    [middleButton setTitle:@"Middle" forState:UIControlStateNormal];
    [self.view addSubview:middleButton];

    nextButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    nextButton.translatesAutoresizingMaskIntoConstraints = NO;
    [nextButton setTitle:@"Next" forState:UIControlStateNormal];
    [self.view addSubview:nextButton];


    [self.view setNeedsUpdateConstraints];

}

Y luego, en updateViewConstrains, primero elimino todas las restricciones, luego creo el diccionario de vistas y luego calculo el espacio que se utilizará entre las vistas. Después de eso, solo uso el formato de lenguaje visual para establecer las restricciones:

- (void)updateViewConstraints {


    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(cancelButton, nextButton, middleButton);

    float distance=(self.view.bounds.size.width-cancelButton.intrinsicContentSize.width-nextButton.intrinsicContentSize.width-middleButton.intrinsicContentSize.width-20-20)/  ([viewsDictionary count]-1);  // 2 times 20 counts for the left & rigth margins
    NSNumber *distancies=[NSNumber numberWithFloat:distance];

//    NSLog(@"Distancies: %@", distancies);
//    
//    NSLog(@"View Width: %f", self.view.bounds.size.width);
//    NSLog(@"Cancel Width: %f", cancelButton.intrinsicContentSize.width);
//    NSLog(@"Middle Width: %f", middleButton.intrinsicContentSize.width);
//    NSLog(@"Next Width: %f", nextButton.intrinsicContentSize.width);



    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[cancelButton]-dis-[middleButton]-dis-[nextButton]-|"
                                                                   options:NSLayoutFormatAlignAllBaseline
                                                                   metrics:@{@"dis":distancies}
                                                                     views:viewsDictionary];


    [self.view addConstraints:constraints];



    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[nextButton]-|"
                                                          options:0
                                                          metrics:nil
                                                            views:viewsDictionary];
    [self.view addConstraints:constraints];



}

Lo bueno de este método es que tienes que hacer muy pocas matemáticas. No digo que esta sea la solución perfecta, pero trabajo para el diseño que estaba tratando de lograr.

Espero que ayude.

Marcal
fuente
2

Aquí hay una solución que centrará verticalmente cualquier número de subvistas, incluso si tienen tamaños únicos. Lo que desea hacer es hacer un contenedor de nivel medio, centrarlo en la supervista, luego colocar todas las subvistas en el contenedor y organizarlas una con respecto a la otra. Pero, de manera crucial, también debe restringirlos a la parte superior e inferior del contenedor, para que el contenedor se pueda dimensionar y centrar correctamente en la supervista. Al calcular la altura correcta a partir de sus subvistas, el contenedor puede centrarse verticalmente.

En este ejemplo, selfes la supervista en la que está centrando todas las subvistas.

NSArray *subviews = @[ (your subviews in top-to-bottom order) ];

UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
for (UIView *subview in subviews) {
    subview.translatesAutoresizingMaskIntoConstraints = NO;
    [container addSubview:subview];
}
[self addSubview:container];

[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];

if (0 < subviews.count) {
    UIView *firstSubview = subviews[0];
    [container addConstraint:[NSLayoutConstraint constraintWithItem:firstSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
    UIView *lastSubview = subviews.lastObject;
    [container addConstraint:[NSLayoutConstraint constraintWithItem:lastSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];

    UIView *subviewAbove = nil;
    for (UIView *subview in subviews) {
        [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
        if (subviewAbove) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                     toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
        }
        subviewAbove = subview;
    }
}
Kevin Conner
fuente
Esta es la mejor solución cuando se utilizan vistas de diferentes tamaños que deben centrarse.
noobsmcgoobs
2

Acabo de resolver mi problema usando la función de multiplicador. No estoy seguro de que funcione para todos los casos, pero para mí funcionó perfectamente. Estoy en Xcode 6.3 FYI.

Lo que terminé haciendo fue:

1) Primero coloque mis botones en una pantalla de 320px de ancho distribuidos de la manera que quería que se viera en un dispositivo de 320px.

Paso 1: posicionar los botones

2) Luego agregué una restricción de espacio inicial para supervisar todos mis botones.

Paso 2: agregue restricciones de espacio principales

3) Luego modifiqué las propiedades del espacio inicial para que la constante fuera 0 y el multiplicador sea el desplazamiento x dividido por el ancho de la pantalla (por ejemplo, mi primer botón tenía 8 píxeles desde el borde izquierdo, así que configuré mi multiplicador en 8/320)

4) Entonces, el paso importante aquí es cambiar el segundo elemento en la relación de restricción para que sea la supervista.Trailing en lugar de superview.leading. Esto es clave porque la supervista. El encabezado es 0 y el final en mi caso es 320, por lo que 8/320 es 8 px en un dispositivo de 320px, luego, cuando el ancho de la supervista cambia a 640 o lo que sea, todas las vistas se mueven en una proporción relativa al ancho del tamaño de pantalla de 320px. Las matemáticas aquí son mucho más simples de entender.

Paso 3 y 4: cambie el multiplicador a xPos / screenWidth y establezca el segundo elemento en .Trailing

ucangetit
fuente
2

Muchas respuestas no son correctas, pero obtienen muchos recuentos. Aquí solo escribo una solución mediante programación, las tres vistas están alineadas horizontalmente, sin usar vistas espaciadoras , pero solo funciona cuando se conocen los anchos de las etiquetas cuando se usan en el guión gráfico .

NSDictionary *views = NSDictionaryOfVariableBindings(_redView, _yellowView, _blueView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_redView(60)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_redView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:0.5 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:1.5 constant:40]];
zgjie
fuente
2

Aquí hay otra respuesta. Estaba respondiendo una pregunta similar y vi un enlace referenciado a esta pregunta. No vi ninguna respuesta similar a la mía. Entonces, pensé en escribirlo aquí.

  class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.whiteColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        view.addSubview(container1)
        view.addSubview(container2)
        view.addSubview(container3)
        view.addSubview(container4)

        [

            // left right alignment
            container1.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            container1.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20),
            container2.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container2.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container3.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container3.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container4.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container4.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),


            // place containers one after another vertically
            container1.topAnchor.constraintEqualToAnchor(view.topAnchor),
            container2.topAnchor.constraintEqualToAnchor(container1.bottomAnchor),
            container3.topAnchor.constraintEqualToAnchor(container2.bottomAnchor),
            container4.topAnchor.constraintEqualToAnchor(container3.bottomAnchor),
            container4.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),


            // container height constraints
            container2.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container3.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container4.heightAnchor.constraintEqualToAnchor(container1.heightAnchor)
            ]
            .forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let view = UIView(frame: .zero)
        view.translatesAutoresizingMaskIntoConstraints = false

        let button = UIButton(type: .System)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle(title, forState: .Normal)
        view.addSubview(button)

        [button.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor),
            button.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
            button.rightAnchor.constraintEqualToAnchor(view.rightAnchor)].forEach { $0.active = true }

        return view
    }
}

ingrese la descripción de la imagen aquí

Y nuevamente, esto se puede hacer con bastante facilidad con iOS9 UIStackViews también.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.greenColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        let stackView = UIStackView(arrangedSubviews: [container1, container2, container3, container4])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .Vertical
        stackView.distribution = .FillEqually
        view.addSubview(stackView)

        [stackView.topAnchor.constraintEqualToAnchor(view.topAnchor),
            stackView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
            stackView.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            stackView.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20)].forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let button = UIButton(type: .Custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.redColor()
        button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
        button.setTitle(title, forState: .Normal)
        let buttonContainer = UIStackView(arrangedSubviews: [button])
        buttonContainer.distribution = .EqualCentering
        buttonContainer.alignment = .Center
        buttonContainer.translatesAutoresizingMaskIntoConstraints = false
        return buttonContainer
    }
}

Tenga en cuenta que es exactamente el mismo enfoque que el anterior. Agrega cuatro vistas de contenedor que se llenan por igual y se agrega una vista a cada vista de pila que está alineada en el centro. Pero, esta versión de UIStackView reduce algo de código y se ve bien.

Sandeep
fuente
1

Otro enfoque podría ser que las etiquetas superior e inferior tengan restricciones relativas a la vista superior e inferior, respectivamente, y que la vista central tenga restricciones superior e inferior con respecto a la primera y tercera vista, respectivamente.

Tenga en cuenta que tiene más control sobre las restricciones de lo que parece al arrastrar las vistas una cerca de la otra hasta que aparezcan líneas punteadas de guiado; esto indica restricciones entre los dos objetos que se formarán en lugar de entre el objeto y la supervista.

En este caso, desearía alterar las restricciones para que sean "mayores o iguales que" el valor deseado, en lugar de "iguales a" para permitirles cambiar su tamaño. No estoy seguro si esto hará exactamente lo que quieres.

Colin
fuente
1

Manera muy fácil de resolver esto en InterfaceBuilder:

Establezca la etiqueta centrada (etiqueta2) en "Centro horizontal en contenedor" y "Centro vertical en contenedor"

Seleccione la etiqueta centrada y la etiqueta superior (etiqueta1 + etiqueta2) y agregue DOS restricciones para el Espaciado vertical. Uno con mayor o igual que el espacio mínimo. Uno con menos que o igual el espacio máximo.

Lo mismo para la etiqueta centrada y la etiqueta inferior (etiqueta2 + etiqueta3).

Además, también puede agregar dos restricciones a label1: espacio superior a SuperView y dos restricciones a label2: espacio inferior a SuperView.

El resultado será que los 4 espacios cambiarán sus tamaños por igual.

sust86
fuente
2
Descubrí que esto solo funciona cuando cambia el tamaño de la supervista para que el espaciado tome exactamente los valores mínimo o máximo. Cuando cambia el tamaño a un valor intermedio, las subvistas no se distribuyen de manera uniforme.
Dorian Roy
1

He hecho una función que podría ayudar. Este ejemplo de uso:

 [self.view addConstraints: [NSLayoutConstraint fluidConstraintWithItems:NSDictionaryOfVariableBindings(button1, button2, button3)
                                                                asString:@[@"button1", @"button2", @"button3"]
                                                               alignAxis:@"V"
                                                          verticalMargin:100
                                                        horizontalMargin:50
                                                             innerMargin:25]];

causará esa distribución vertical (lo siento, no tengo la reputación de 10 para incrustar imágenes). Y si cambia el eje y algunos valores de margen:

alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10

Obtendrás esa distribución horizontal .

Soy novato en iOS pero voilà!

EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of subviews
 * to be evenly distributed along an axis.
 */
+ (NSArray *)  fluidConstraintWithItems:(NSDictionary *) views
                               asString:(NSArray *) stringViews
                              alignAxis:(NSString *) axis
                         verticalMargin:(NSUInteger) vMargin
                       horizontalMargin:(NSUInteger) hMargin
                            innerMargin:(NSUInteger) inner;
@end

EvenDistribution.m

#import "EvenDistribution.h"

@implementation NSLayoutConstraint (EvenDistribution)

+ (NSArray *) fluidConstraintWithItems:(NSDictionary *) dictViews
                              asString:(NSArray *) stringViews
                             alignAxis:(NSString *) axis
                        verticalMargin:(NSUInteger) vMargin
                      horizontalMargin:(NSUInteger) hMargin
                           innerMargin:(NSUInteger) iMargin

{
    NSMutableArray *constraints = [NSMutableArray arrayWithCapacity: dictViews.count];
    NSMutableString *globalFormat = [NSMutableString stringWithFormat:@"%@:|-%d-",
                                     axis,
                                     [axis isEqualToString:@"V"] ? vMargin : hMargin
                                     ];



        for (NSUInteger i = 0; i < dictViews.count; i++) {

            if (i == 0)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@]-%d-", stringViews[i], iMargin]];
            else if(i == dictViews.count - 1)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-", stringViews[i], stringViews[i-1]]];
            else
               [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-%d-", stringViews[i], stringViews[i-1], iMargin]];

            NSString *localFormat = [NSString stringWithFormat: @"%@:|-%d-[%@]-%d-|",
                                     [axis isEqualToString:@"V"] ? @"H" : @"V",
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin,
                                     stringViews[i],
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin];

            [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:localFormat
                                                                                     options:0
                                                                                     metrics:nil
                                                                                       views:dictViews]];


    }
    [globalFormat appendString:[NSString stringWithFormat:@"%d-|",
                                [axis isEqualToString:@"V"] ? vMargin : hMargin
                                ]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:globalFormat
                                                                             options:0
                                                                             metrics:nil
                                                                               views:dictViews]];

    return constraints;

}

@end
penumbra
fuente
1

Sí, puede hacerlo únicamente en el generador de interfaces y sin escribir código; la única advertencia es que está cambiando el tamaño de la etiqueta en lugar de distribuir espacios en blanco. En este caso, alinee X e Y de Label 2 con la supervista para que quede fijo en el centro. Luego establezca el espacio vertical de la etiqueta 1 en la supervista y la etiqueta 2 en el estándar, repita para la etiqueta 3. Después de configurar la etiqueta 2, la forma más fácil de configurar las etiquetas 1 y 3 es cambiar su tamaño hasta que encajen.

Aquí está la pantalla horizontal, tenga en cuenta que el espacio vertical entre las etiquetas 1 y 2 está configurado como estándar:pantalla horizontal

Y aquí está la versión vertical:ingrese la descripción de la imagen aquí

Me doy cuenta de que no están absolutamente 100% igualmente espaciados entre las líneas de base debido a la diferencia entre el espacio estándar entre etiquetas y el espacio estándar para la supervista. Si eso te molesta, establece el tamaño en 0 en lugar de estándar

James
fuente
1

Establecí un valor de ancho solo para el primer elemento (> = un ancho) y una distancia mínima entre cada elemento (> = una distancia). Luego uso Ctrl para arrastrar el segundo, tercer ... elemento en el primero para encadenar las dependencias entre los elementos.

ingrese la descripción de la imagen aquí

Vladimír Slavík
fuente
1

Tarde a la fiesta, pero tengo una solución de trabajo para crear un menú horizontalmente con espaciado. Se puede hacer fácilmente usando ==NSLayoutConstraint

const float MENU_HEIGHT = 40;

- (UIView*) createMenuWithLabels: (NSArray *) labels
    // labels is NSArray of NSString
    UIView * backgroundView = [[UIView alloc]init];
    backgroundView.translatesAutoresizingMaskIntoConstraints = false;

    NSMutableDictionary * views = [[NSMutableDictionary alloc] init];
    NSMutableString * format = [[NSMutableString alloc] initWithString: @"H:|"];
    NSString * firstLabelKey;

    for(NSString * str in labels)
    {
        UILabel * label = [[UILabel alloc] init];
        label.translatesAutoresizingMaskIntoConstraints = false;
        label.text = str;
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor = [UIColor whiteColor];
        [backgroundView addSubview: label];
        [label fixHeightToTopBounds: MENU_HEIGHT-2];
        [backgroundView addConstraints: [label fixHeightToTopBounds: MENU_HEIGHT]];
        NSString * key = [self camelCaseFromString: str];
        [views setObject: label forKey: key];
        if(firstLabelKey == nil)
        {
            [format appendString: [NSString stringWithFormat: @"[%@]", key]];
            firstLabelKey = key;
        }
        else
        {
            [format appendString: [NSString stringWithFormat: @"[%@(==%@)]", key, firstLabelKey]];
        }
    }

    [format appendString: @"|"];

    NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                               options: 0
                                                                               metrics: nil
                                                                                 views: (NSDictionary *) views];
    [backgroundView addConstraints: constraints];
    return backgroundView;
}
tsuz
fuente
0

Android tiene un método para encadenar vistas en su sistema de diseño basado en restricciones que quería imitar. Las búsquedas me trajeron aquí, pero ninguna de las respuestas funcionó. No quería usar StackViews porque tienden a causarme más dolor en el futuro de lo que ahorran por adelantado. Terminé creando una solución que usaba UILayoutGuides colocadas entre las vistas. Controlar su ancho permite diferentes tipos de distribuciones, estilos de cadena en el lenguaje Android. La función acepta un anclaje inicial y final en lugar de una vista principal. Esto permite que la cadena se coloque entre dos vistas arbitrarias en lugar de distribuirse dentro de la vista principal. Utiliza el UILayoutGuideque solo está disponible en iOS 9+, pero eso ya no debería ser un problema.

public enum LayoutConstraintChainStyle {
    case spread //Evenly distribute between the anchors
    case spreadInside //Pin the first & last views to the sides and then evenly distribute
    case packed //The views have a set space but are centered between the anchors.
}

public extension NSLayoutConstraint {

    static func chainHorizontally(views: [UIView],
                                  leadingAnchor: NSLayoutXAxisAnchor,
                                  trailingAnchor: NSLayoutXAxisAnchor,
                                  spacing: CGFloat = 0.0,
                                  style: LayoutConstraintChainStyle = .spread) -> [NSLayoutConstraint] {
    var constraints = [NSLayoutConstraint]()
    guard views.count > 1 else { return constraints }
    guard let first = views.first, let last = views.last, let superview = first.superview else { return constraints }

    //Setup the chain of views
    var distributionGuides = [UILayoutGuide]()
    var previous = first
    let firstGuide = UILayoutGuide()
    superview.addLayoutGuide(firstGuide)
    distributionGuides.append(firstGuide)
    firstGuide.identifier = "ChainDistribution\(distributionGuides.count)"
    constraints.append(firstGuide.leadingAnchor.constraint(equalTo: leadingAnchor))
    constraints.append(first.leadingAnchor.constraint(equalTo: firstGuide.trailingAnchor, constant: spacing))
    views.dropFirst().forEach { view in
        let g = UILayoutGuide()
        superview.addLayoutGuide(g)
        distributionGuides.append(g)
        g.identifier = "ChainDistribution\(distributionGuides.count)"
        constraints.append(contentsOf: [
            g.leadingAnchor.constraint(equalTo: previous.trailingAnchor),
            view.leadingAnchor.constraint(equalTo: g.trailingAnchor)
        ])
        previous = view
    }
    let lastGuide = UILayoutGuide()
    superview.addLayoutGuide(lastGuide)
    constraints.append(contentsOf: [lastGuide.leadingAnchor.constraint(equalTo: last.trailingAnchor),
                                    lastGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
    distributionGuides.append(lastGuide)

    //Space the according to the style.
    switch style {
    case .packed:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalTo: first.widthAnchor))
            constraints.append(contentsOf:
                distributionGuides.dropFirst().dropLast()
                    .map { $0.widthAnchor.constraint(equalToConstant: spacing) }
                )
        }
    case .spread:
        if let first = distributionGuides.first {
            constraints.append(contentsOf:
                distributionGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: first.widthAnchor) })
        }
    case .spreadInside:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(equalToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalToConstant: spacing))
            let innerGuides = distributionGuides.dropFirst().dropLast()
            if let key = innerGuides.first {
                constraints.append(contentsOf:
                    innerGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: key.widthAnchor) }
                )
            }
        }
    }

    return constraints
}
Sam Corder
fuente
0

Quería alinear horizontalmente 5 imágenes, así que terminé siguiendo la respuesta de Mete con una pequeña diferencia.

La primera imagen se centrará horizontalmente en un contenedor igual a 0 y un multiplicador de 1: 5:

primera imagen

La segunda imagen se centrará horizontalmente en un contenedor igual a 0 y un multiplicador de 3: 5: segunda imagen

Y así para el resto de las imágenes. Por ejemplo, la quinta (y última) imagen se centrará horizontalmente en un contenedor igual a 0 y un multiplicador de 9: 5: última imagen

Como explicó Mete, el orden va 1, 3, 5, 7, 9, etc. Las posiciones siguen la misma lógica: la primera posición es 1, luego el espacio, luego la siguiente posición 3, y así sucesivamente.

Adriana Pineda
fuente
-1

¿Por qué no creas una tableView y haces isScrollEnabled = false

Josh O'Connor
fuente
No entiendo el voto negativo, mi solución es pensar fuera de la caja. ¿Por qué pasar por el dolor de cabeza de crear una lista con autolayout cuando el cacao le da una lista en UITableView?
Josh O'Connor