ios - tesis - Distribuya uniformemente varias vistas dentro de una vista de contenedor
tesis sobre youtube pdf (27)
Auto Layout está haciendo mi vida difícil. En teoría, iba a ser realmente útil cuando cambié, pero parece que peleo todo el tiempo.
He hecho 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 cuando la vista se redimensiona?
Aquí hay tres etiquetas (espaciadas manualmente incluso verticalmente):
Lo que quiero es que cambien el tamaño de su espaciado (no el tamaño de la vista) de manera uniforme cuando gire. Por defecto, las vistas superior e inferior aplastan hacia el centro:
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 generador de interfaz 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 configurar la propiedad Distribución en ''Espaciado igual'':
Por supuesto, si necesita compatibilidad con iOS 8 o inferior, tendrá que elegir una de las otras opciones.
Acabo de resolver mi problema utilizando 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, coloqué mis botones en una pantalla de 320px de ancho distribuida de la forma en que quería que luciera en un dispositivo de 320px.
2) Luego agregué una restricción de espacio inicial para supervisar todos mis botones.
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 fue 8px desde el borde izquierdo, así que puse 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 supervisado. Recorrer en lugar de supervisar. Dirigir. Esta es la clave porque superview.Leading 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 del superview 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. La matemática aquí es mucho más simple de entender.
Aquí está 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
}
}
Y, de nuevo, esto también se puede hacer con facilidad con iOS9 UIStackViews.
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 algunos códigos y se ve bien.
Aquí hay una solución que centrará verticalmente cualquier número de subvistas, incluso si tienen tamaños únicos. Lo que desea hacer es crear un contenedor de nivel medio, centrarlo en la vista de supervisión, luego colocar todas las subvistas en el contenedor y organizarlas una con respecto a la otra. Pero lo más importante es que también debe restringirlos a la parte superior e inferior del contenedor, de modo que el contenedor se pueda dimensionar y centrar correctamente en la vista de supervisión. Al calcular la altura correcta a partir de sus subvistas, el contenedor puede centrarse verticalmente.
En este ejemplo, self
es la supervisión 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;
}
}
Así que mi enfoque le permite hacer esto en el constructor de interfaces. Lo que haces es crear ''vistas espaciadoras'' que has configurado para que coincidan con las alturas por igual. Luego, agregue restricciones superiores e inferiores a las etiquetas (vea la captura de pantalla).
Más específicamente, tengo una restricción superior en ''Spacer View 1'' para supervisar con una restricción de altura de prioridad más baja que 1000 y con Height Equals a todas las demás ''vistas espaciadoras''. ''Spacer View 4'' tiene una restricción de espacio inferior para supervisar. Cada etiqueta tiene sus respectivas restricciones superior e inferior a sus ''vistas espaciadoras'' más cercanas.
Nota: Asegúrese de NO tener restricciones de espacio superior / inferior en sus etiquetas para supervisar; Solo los de las ''vistas del espacio''. Esto será satisfactorio ya que las restricciones superior e inferior están en ''Space View 1'' y ''Spacer View 4'' respectivamente.
Duh 1: duplicé 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.
Con las 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, todos obtendrán el mismo ancho ... Puede hacerlo tanto horizontalmente (H) como verticalmente (V).
Echa un vistazo a la biblioteca de código abierto PureLayout . Ofrece algunos métodos API para distribuir vistas, incluidas las variantes donde 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".
Desde 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;
// ...
Ya que todo es de código abierto, si está interesado en ver cómo se logra esto sin las vistas espaciador, simplemente eche un vistazo a la implementación. (Depende de aprovechar tanto la constant
como el multiplier
para las restricciones).
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 coloca algunas vistas invisibles entre tus campos y dile a la disposición automática que los mantenga del mismo tamaño. ¡Funciona perfectamente!
Una cosa de la 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 se cambiaba el tamaño en una cantidad impar. De lo contrario funcionó perfectamente.
Edición: encontré que el conflicto se convirtió en un problema. Debido a eso, tomé una de las restricciones de espaciado, la eliminé y la reemplacé con dos restricciones, una mayor o igual y una menor o igual. Ambos eran del mismo tamaño y tenían una prioridad mucho menor que las otras restricciones. El resultado no fue más conflicto.
Estoy teniendo un problema similar y descubrí este post. Sin embargo, ninguna de las respuestas proporcionadas actualmente resuelve el problema de la manera deseada. 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.
Hay 3 vistas, todas de 20 puntos de altura. El uso de cualquiera de los métodos sugeridos distribuye por igual los centros de las vistas y le proporciona el diseño ilustrado. Observe que el centro y de las vistas están espaciados igualmente. Sin embargo, el espacio entre la vista superior 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 de manera equitativa, ambas deben ser de 10 puntos, es decir, todas las flechas azules deben ser de 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 establecer las alturas de las vistas de espaciado para que sean iguales.
He estado en una montaña rusa de amorosa autolayout y odiarlo. La clave para amarlo parece ser aceptar lo siguiente:
- La edición del constructor de interfaces y la creación automática de restricciones "útil" son casi inútiles para todos, excepto en el caso más trivial
- Crear categorías para simplificar 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 está muy simplificado con un par de métodos de categoría en UIView
, pero para mayor claridad, lo he hecho hasta aquí.
La categoría está here para los interesados, y tiene un método para espaciar uniformemente un conjunto de vistas a lo largo de un eje particular.
La forma más fácil y correcta es usar las vistas de pila.
- Agregue sus etiquetas / vistas a la vista de pila :
- Seleccione la vista de pila y establezca la distribución para que tenga el mismo espaciado :
- Agregue espaciado a las restricciones de vecinos más cercanos a la vista de pila y actualice los marcos:
- Añadir restricciones de altura a todas las etiquetas:
- Disfrute de vista previa:
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 uniformemente? 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 supervisión:
[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
Muchas respuestas no son correctas, pero obtienen muchas cuentas. Aquí simplemente escribo una solución mediante programación, las tres vistas son alineación horizontal, 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]];
Pude resolver esto completamente en IB:
- Haga restricciones para alinear el centro Y de cada una de sus subvistas con el borde inferior de la supervisión.
- 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.
Sé que ha pasado un tiempo desde la primera respuesta, pero acabo de encontrar el mismo problema y quiero compartir mi solución. Para las generaciones venideras ...
Puse 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, simplemente 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 poco matemática. No estoy diciendo que esta sea la solución perfecta, pero trabajo para el diseño que estaba tratando de lograr.
Espero que ayude.
Sobre la base de la respuesta de Ben Dolman, esto distribuye las vistas de manera más equitativa (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;
}
consulte https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html con una buena descripción sobre cómo resolver su problema.
¿Por qué no creas un tableView y make? isScrollEnabled = false
¡MIRA, SIN ESPACIADORES!
Basándome en las sugerencias en la sección de comentarios de mi respuesta original, especialmente en las sugerencias útiles de @ Rivera, he simplificado mi respuesta original.
Estoy usando gifs para ilustrar lo simple que es esto. Espero que encuentres útiles los gifs. En caso de que tenga un problema con los gifs, he incluido la respuesta anterior a continuación con capturas de pantalla.
Instrucciones:
1) Añade tus botones o etiquetas. Estoy usando 3 botones.
2) Agregue una restricción de centro x de cada botón a la supervisión:
3) Agregue una restricción de cada botón a la restricción de diseño inferior:
4) Ajuste la restricción agregada en el # 3 anterior de la siguiente manera:
a) seleccione la restricción, b) elimine la constante (configurada en 0), c) cambie el multiplicador de la siguiente manera: tome el número de botones + 1, y comience en la parte superior, establezca el multiplicador como buttonCountPlus1: 1 , y luego buttonCountPlus1 : 2 , y finalmente buttonCountPlus1: 3 . (Le explico de dónde obtuve esta fórmula en la respuesta anterior a continuación, si está interesado).
5) Aquí hay una demo corriendo!
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.
Respuesta antigua
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 necesitas es una fórmula matemática llamada "fórmula de sección". Es más simple de hacer que de explicar. Lo haré lo mejor que pueda al demostrarlo en IB, pero es tan 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. Sólo tienes que controlar arrastrar desde cada etiqueta hasta la parte inferior.
2) Mantenga presionada la tecla shift, ya que también podría agregar la otra restricción que vamos a utilizar, a saber, la "guía de diseño del espacio inferior al inferior".
3) Seleccione la "guía de distribución de espacio inferior a inferior" y "centrar horizontalmente en el contenedor". Haga esto para las 3 etiquetas.
Básicamente, si tomamos la etiqueta cuyas coordenadas deseamos determinar y la dividimos por el número total de etiquetas más 1, 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 vertical y horizontal al mismo tiempo. Es super poderoso!
Aquí están nuestros multiplicadores.
Etiqueta1 = 1/4 = .25,
Label2 = 2/4 = .5,
Label3 = 3/4 = .75
(Editar: @Rivera comentó que simplemente puedes usar las proporciones directamente en el campo del multiplicador, y xCode con la matemática).
4) Entonces, seleccionemos Label1 y seleccionemos la restricción inferior. Me gusta esto:
5) Seleccione el "Segundo elemento" en el inspector de atributos.
6) Desde el menú desplegable, seleccione "Invertir primero y segundo elemento".
7) Poner a cero la constante y el valor de wCn de wC. (Podrías agregar un offset aquí si lo necesitaras).
8) Esta es la parte crítica: en el campo del multiplicador, agregue nuestro primer multiplicador 0.25.
9) Mientras lo hace, configure el "Primer elemento" superior en "CentroY" ya que queremos centrarlo en el centro de la etiqueta. Así es como debería verse todo eso.
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! Super 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 guiones gráficos, no en xibs. Utilice ''Espacio inferior al contenedor'' en xibs. ¡Buena atrapada!
Muy rápida solución Interface Builder:
Para que cualquier número de vistas esté espaciado uniformemente dentro de una supervisión, simplemente asigne a cada uno la restricción "Alinear Centro X para supervisar" para diseño horizontal, o "Alinear Centro Y supervisar" para diseño vertical, y configure el Multiplicador como N:p
( NOTA: algunos han tenido mejor suerte con p:N
- ver más abajo )
dónde
N = total number of views
, y
p = position of the view including spaces
La primera posición es 1, luego un espacio, haciendo la siguiente posición 3, así que p se convierte en una serie [1,3,5,7,9, ...]. Funciona para cualquier número de vistas.
Entonces, si tienes 3 vistas para espaciar, se ve así:
EDITAR Nota: La elección de N:p
o p:N
depende del orden de relación de su restricción de alineación. Si "Primer elemento" es Superview.Center, puede usar p:N
, mientras que Superview.Center es "Segundo elemento", puede usar N:p
. En caso de duda, pruebe ambos ... :-)
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 superiores e inferiores relativas a la primera y tercera vista, respectivamente.
Tenga en cuenta que tiene más control sobre las restricciones de lo que podría parecer al arrastrar las vistas una cerca de la otra hasta que aparezcan líneas de guiones, lo que indica restricciones entre los dos objetos que se formarán en lugar de entre el objeto y la supervisión.
En este caso, desearía modificar las restricciones para que sean "mayores o iguales que" el valor deseado, en lugar de "iguales a" para permitirles cambiar el tamaño. No estoy seguro de si esto hará exactamente lo que quieres.
Una forma muy fácil de resolver esto en InterfaceBuilder:
Establezca la etiqueta centrada (label2) en "Centro horizontal en contenedor" y "Centro vertical en contenedor"
Seleccione la etiqueta centrada y la etiqueta superior (label1 + label2) y agregue DOS restricciones para el espaciado vertical. Uno con mayor o igual que el espacio mínimo. Uno con menor o igual que el espaciado máximo.
Lo mismo para la etiqueta centrada y la etiqueta inferior (label2 + label3).
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 las 4 separaciones cambiarán sus tamaños por igual.
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)
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 ... del primero para encadenar dependencias entre los elementos.
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 insertar imágenes). Y si cambias 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
Llego tarde a la fiesta, pero tengo una solución de trabajo para crear un menú horizontalmente con espacio. Se puede hacer fácilmente usando ==
en 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;
}
Sí, puede hacer esto únicamente en el creador de interfaces y sin escribir código; la única advertencia es que está redimensionando la etiqueta en lugar de distribuir espacios en blanco. En este caso, alinee las etiquetas X e Y de la etiqueta 2 con la vista de supervisión para que quede fija en el centro. Luego, configure el espacio vertical de la etiqueta 1 en la vista de supervisión 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 redimensionarlas hasta que se ajusten.
Aquí está la pantalla horizontal, tenga en cuenta que el espacio vertical entre las etiquetas 1 y 2 se establece en estándar:
Y aquí está la versión de retrato:
Me doy cuenta de que no están absolutamente separados al 100% entre las líneas de base debido a la diferencia entre el espacio estándar entre las etiquetas y el espacio estándar a la vista de supervisión. Si eso te molesta, establece el tamaño en 0 en lugar de estándar