Espaciado uniformemente varias filas de un número arbitrario de elementos utilizando NSLayoutConstraints

Tengo una pregunta sobre el uso de restricciones de layout para el siguiente escenario. Todo está en código (sin plumillas o storyboards). Quiero crear una vista que contenga un número arbitrario de filas de elementos. Cada fila contiene una cantidad arbitraria de subvistas.

Para crear esta vista, quiero pasar en una matriz que tiene dos niveles de profundidad. La matriz de primer nivel contiene cada fila. La matriz de segundo nivel contiene los elementos en cada fila. Entonces, por ejemplo, esto podría verse así:

NSArray *elements = @[@[subview1, subview2, subview3], @[subview4], @[subview5, subview6]]

En esta matriz, habría 3 filas:

1) Fila 1: subview1, subview2, subview3
2) Fila 2: subview4
3) Fila 3: subvista5, subvista6

Quiero que estos elementos se formateen como:

-Las filas deben ser todo el ancho de la vista principal (para esto podemos suponer que es el tamaño de la pantalla)
– Cada uno de los elementos en una fila debe tener el mismo ancho y tener la misma cantidad de espacio entre ellos (por ejemplo, si hay 4 elementos, el espacio entre 1 y 2 y 2 y 3 podría ser de 10 pt)
– Cada una de las filas debe tener la misma cantidad de espacio vertical entre ellas (por ejemplo, 10pp de espacio vertical entre cada fila)

En el escenario anterior, la fila 1 tendría 3 subvistas con igual ancho espaciado a partes iguales, la fila 2 tendría 1 subvista que ocupa todo el ancho de la fila, y la fila 3 tendría 2 subvistas que tienen el mismo ancho y estarían igualmente espaciadas aparte.

Entonces, la pregunta es, ¿cómo hago esto?

He estado trabajando en esto por un time y mi comprensión no parece estar mejorando. ¡La ayuda sería apreciada!

Algo así debería funcionar, creo. Traté de hacer en general, por lo que debería funcionar para diferentes cantidades de filas y elementos por fila.

 #define subviewHeight 44 #define spaceFromTop 10 #define spaceFromSide 10 #define subviewVerticalSpacing 10 #define subviewHorizontalSpacing 10 @implementation ViewController - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; UIButton *subview1 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview1 setTitle:@"View 1" forState:UIControlStateNormal]; UIButton *subview2 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview2 setTitle:@"View 2" forState:UIControlStateNormal]; UIButton *subview3 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview3 setTitle:@"View 3" forState:UIControlStateNormal]; UIButton *subview4 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview4 setTitle:@"View 4" forState:UIControlStateNormal]; UIButton *subview5 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview5 setTitle:@"View 5" forState:UIControlStateNormal]; UIButton *subview6 = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [subview6 setTitle:@"View 6" forState:UIControlStateNormal]; NSArray *arrayOfSubviews = @[@[subview1, subview2, subview3], @[subview4], @[subview5, subview6]]; [self addSubviewsWithConstraints:arrayOfSubviews]; } -(void)addSubviewsWithConstraints:(NSArray *) arrayOfArrays { NSMutableArray *arrayOfViewsDicts = [NSMutableArray array]; // make the views dictionaries needed for the views parameter of constraintsWithVisualFormat:options:metrics:views:. One for each row. for (NSArray *array in arrayOfArrays) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (int i = 0; i < array.count; i++) { [dict setObject:array[i] forKey:[NSString stringWithFormat:@"view%d",i]]; } [arrayOfViewsDicts addObject:dict]; } for (int i=0; i<arrayOfArrays.count; i++) { NSString *formatString = [self makeConstraintsForRowOfSubviews:arrayOfArrays[i] withViewsDict:arrayOfViewsDicts[i]]; // Make the format string for a row of subviews for (id subview in arrayOfArrays[i]) { [subview setTranslatesAutoresizingMaskIntoConstraints:NO]; [self.view addSubview:subview]; NSLayoutConstraint *heightCon = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:subviewHeight]; [subview addConstraint:heightCon]; NSLayoutConstraint *verticalCon = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:spaceFromTop + ((subviewHeight + subviewVerticalSpacing)*i)]; [self.view addConstraint:verticalCon]; } NSArray *cons = [NSLayoutConstraint constraintsWithVisualFormat:formatString options:NSLayoutFormatAlignAllBottom metrics:nil views:arrayOfViewsDicts[i]]; [self.view addConstraints:cons]; } } -(NSString *) makeConstraintsForRowOfSubviews:(NSArray *) arrayOfSubviews withViewsDict:(NSDictionary *) viewsDict { NSMutableString *formatString = [NSMutableString string]; for (int i = 0; i<arrayOfSubviews.count; i++) { if (i == 0) { [formatString appendFormat:@"|-%d-[%@]",spaceFromSide,[viewsDict allKeysForObject:arrayOfSubviews[i]][0]]; }else{ [formatString appendFormat:@"-%d-[%@(==view0)]",subviewHorizontalSpacing,[viewsDict allKeysForObject:arrayOfSubviews[i]][0]]; } } [formatString appendFormat:@"-%d-|",spaceFromSide]; return formatString; } 

Este código produjo este resultado:

introduzca la descripción de la imagen aquí

Podría hacerlo con autolayout, pero creo que su código sería más fácil de entender si utilizara un UICollectionView con su propia subclass UICollectionViewLayout personalizada.

Si realmente quieres hacerlo con autolayout, creo que deberías hacer que tu jerarquía de vista coincida con la jerarquía de tu matriz, al introducir un nivel medio de vistas de "fila":

 gridView ├─ rowView0 │ ├─ subview1 │ ├─ subview2 │ └─ subview3 ├─ rowView1 │ └─ subview4 └─ rowView2 ├─ subview5 └─ subview6 

Ahora puede establecer restricciones en las filas como esta:

 // These constraints force the rows to collectively fill gridView: rowView0.top = gridView.top rowView1.top = rowView0.bottom + 10 rowView2.top = rowView1.bottom + 10 gridView.bottom = rowView2.bottom // These constraints force the rows to have equal heights: rowView1.height = rowView0.height rowView2.height = rowView0.height // These constraints force the rows to each be the width of gridView: rowView0.leading = gridView.leading rowView0.trailing = gridView.trailing rowView1.leading = gridView.leading rowView1.trailing = gridView.trailing rowView2.leading = gridView.leading rowView2.trailing = gridView.trailing 

Esas restricciones deberían ser necesarias (es decir, no excesivamente restringidas) y suficientes (es decir, no ambiguas).

Luego puede aplicar el mismo esquema de restricción a cada fila, con los ejes X e Y intercambiados. Por ejemplo, usaría estas restricciones en la fila 0:

 // These constraints collectively force the cells to fill rowView0: subview1.leading = rowView0.leading subview2.leading = subview1.trailing + 10 subview3.leading = subview2.trailing + 10 rowView0.trailing = subview3.trailing // These constraints force the cells to have equal widths: subview2.width = subview1.width subview3.width = subview1.width // These constraints force the cells to each be the height of rowView0: subview1.height = rowView0.height subview2.height = rowView0.height subview3.height = rowView0.height