Centrar una vista en su supervista usando Visual Format Language

160

Recién comencé a aprender AutoLayout para iOS y eché un vistazo al lenguaje de formato visual.

Todo funciona bien, excepto por una cosa: simplemente no puedo obtener una vista para centrar dentro de su supervista.
¿Es esto posible con VFL o necesito crear manualmente una restricción yo mismo?

Christian Schnorr
fuente

Respuestas:

232

Actualmente, no, no parece que sea posible centrar una vista en la supervista usando solo VFL. Sin embargo, no es tan difícil hacerlo usando una sola cadena VFL y una sola restricción adicional (por eje):

VFL: "|-(>=20)-[view]-(>=20)-|"

[NSLayoutConstraint constraintWithItem:view
                             attribute:NSLayoutAttributeCenterX
                             relatedBy:NSLayoutRelationEqual
                                toItem:view.superview
                             attribute:NSLayoutAttributeCenterX
                            multiplier:1.f constant:0.f];

Uno podría pensar que simplemente sería capaz de hacer esto (que es lo que inicialmente pensé e intenté cuando vi esta pregunta):

[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=20)-[view(==200)]-(>=20)-|"
                                 options: NSLayoutFormatAlignAllCenterX | NSLayoutFormatAlignAllCenterY
                                 metrics:nil
                                   views:@{@"view" : view}];

Intenté muchas variaciones diferentes de lo anterior tratando de doblarlo a mi voluntad, pero esto no parece aplicarse a la supervista incluso cuando explícitamente tiene dos cadenas VFL separadas para ambos ejes ( H:|V:). Entonces empecé a tratar de aislar exactamente cuando las opciones no obtener aplicado a la VFL. Parecen no aplicarse a la supervista en el VFL y solo se aplicarán a las vistas explícitas que se mencionan en la cadena de VFL (lo que es decepcionante en ciertos casos).

Espero que en el futuro Apple agregue algún tipo de nueva opción para que las opciones de VFL tengan en cuenta la supervista, incluso si lo hacen solo cuando solo hay una vista explícita aparte de la supervista en la VFL. Otra solución podría ser otra opción pasada en el VFL que dice algo como: NSLayoutFormatOptionIncludeSuperview.

No hace falta decir que aprendí mucho sobre VFL tratando de responder esta pregunta.

larsacus
fuente
2
Gracias por esa respuesta detallada. Sin embargo, la única razón para usar VFL es deshacerme de esos feos trozos de restricciones. Lástima que Apple todavía no sea compatible con esto ...
Christian Schnorr
@larsacus ¿Podría comentar por qué funciona la respuesta a continuación? Me tiene perplejo.
Matt G
8
La respuesta de Evgeny a continuación funciona porque el motor de distribución automática trata |y [superview]como entidades internas separadas a pesar de que representan el mismo objeto semántico. Al usar [superview], la distribución automática incluye el objeto de supervista en sus métricas de alineación (en el caso de su respuesta en el enlace github, es la NSLayoutFormatAlignAllCenterY/ Xmétrica).
larsacus
@larsacus ¡Genial, gracias!
Matt G
¿Sabemos si la limitación aún se aplica con la versión de VFL que se incluye con iOS 7.x actual?
Drux
138

Sí, es posible centrar una vista en su supervista con Visual Format Language. Tanto vertical como horizontalmente. Aquí está la demostración:

https://github.com/evgenyneu/center-vfl

Evgenii
fuente
15
Esto es increíble, ¿crees que podrías ampliar esto y explicar por qué funciona?
tapi
3
Terminé utilizando algo más cercano a la respuesta marcada, pero +1 para un archivo adjunto de github en el suyo
Rembrandt Q. Einstein
44
@Dan Si no funciona para usted, es posible que también necesite tener restricciones de ancho y alto. El VFL sería así para el ancho: @ "[etiqueta (== 200)]" y así para la altura: @ "V: [etiqueta (== 200)]"
Jonathan Zhan
1
¿Por qué los resultados de | y [superview] diferente? ¿Es esto algo que debería ser rastreado o está sucediendo algo que simplemente no estoy siguiendo?
Matt G
3
No se puede analizar el formato de restricción: las opciones enmascaran las vistas que se deben alinear en un borde horizontal, lo que no está permitido para el diseño que también es horizontal. H: [supervista] - (<= 1) - [etiqueta1]
grabner
82

Creo que es mejor crear restricciones manualmente.

[superview addConstraint:
 [NSLayoutConstraint constraintWithItem:view
                              attribute:NSLayoutAttributeCenterX
                              relatedBy:NSLayoutRelationEqual
                                 toItem:superview
                              attribute:NSLayoutAttributeCenterX
                             multiplier:1
                               constant:0]];
[superview addConstraint:
 [NSLayoutConstraint constraintWithItem:view
                              attribute:NSLayoutAttributeCenterY
                              relatedBy:NSLayoutRelationEqual
                                 toItem:superview
                              attribute:NSLayoutAttributeCenterY
                             multiplier:1
                               constant:0]];

Ahora podemos usar NSLayoutAnchor para definir mediante programación AutoLayout :

(Disponible en iOS 9.0 y posterior)

view.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true

Recomiendo usar SnapKit , que es un DSL para facilitar el diseño automático en iOS y macOS.

view.snp.makeConstraints({ $0.center.equalToSuperview() })
jqgsninimo
fuente
1
Funcionó mejor en mi contexto, aunque no hay VFL.
brainray
me da una advertencia:Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Tapas Pal
10

Absolutamente posible pude hacerlo así:

[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-(<=1)-[subview]"
                                        options:NSLayoutFormatAlignAllCenterY
                                        metrics:nil
                                          views:NSDictionaryOfVariableBindings(view, subview)];

Aprendí esto de aquí. El código anterior centra la subvista a la vista por Y obviamente.

Swift3:

        NSLayoutConstraint.constraints(withVisualFormat: "H:[view]-(<=1)-[subview]",
                                       options: .alignAllCenterY,
                                                       metrics: nil,
                                                       views: ["view":self, "subview":_spinnerView])
Igor Muzyka
fuente
7

Agregue el siguiente código y tenga su botón en el centro de la pantalla. Absolutamente posible

[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"H:|-[button]-|"
                           options:NSLayoutFormatAlignAllCenterY
                           metrics:0
                           views:views]];

[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"V:|-[button]-|"
                           options:NSLayoutFormatAlignAllCenterX
                           metrics:0
                           views:views]];
Pankaj Gaikar
fuente
1

Sé que no es lo que quieres pero, por supuesto, puedes calcular los márgenes y usarlos para crear la cadena de formato visual;)

De todos modos, no. Desafortunadamente, no es posible hacer eso 'automáticamente' con VFL, al menos todavía no.

Tobi
fuente
Bueno, puedes usar NSString stringWithFormat: para construir tu cadena VFL dinámicamente en tiempo de ejecución.
TRVD1707
1

Gracias a la respuesta de @Evgenii, creo un ejemplo completo en gist:

centre el cuadrado rojo verticalmente con una altura personalizada

https://gist.github.com/yallenh/9fc2104b719742ae51384ed95dcaf626

Puede centrar una vista verticalmente usando VFL (que no es del todo intuitivo) o usar restricciones manualmente. Por cierto, no puedo combinar el centro y la altura juntos en VFL como:

"H:[subView]-(<=1)-[followIcon(==customHeight)]"

En la solución actual, las restricciones de VFL se agregan por separado.

Allen Huang
fuente
0

Lo que hay que hacer es que la supervista se declare en el diccionario. En lugar de usarlo |usas @{@"super": view.superview};.

Y NSLayoutFormatAlignAllCenterXpara vertical y NSLayoutFormatAlignAllCenterYpara horizontal como opciones.

lagos
fuente
0

Puedes usar vistas adicionales.

NSDictionary *formats =
@{
  @"H:|[centerXView]|": @0,
  @"V:|[centerYView]|": @0,
  @"H:[centerYView(0)]-(>=0)-[yourView]": @(NSLayoutFormatAlignAllCenterY),
  @"V:[centerXView(0)]-(>=0)-[yourView]": @(NSLayoutFormatAlignAllCenterX),
};
NSDictionary *views = @{
  @"centerXView": centerXView, 
  @"centerYView": centerYView, 
  @"yourView": yourView,
};
 ^(NSString *format, NSNumber *options, BOOL *stop) {
     [superview addConstraints:
      [NSLayoutConstraint constraintsWithVisualFormat:format
                                              options:options.integerValue
                                              metrics:nil
                                                views:views]];
 }];

Si lo quieres ordenado, entonces centerXView.hidden = centerYView.hidden = YES;.

PD: No estoy seguro de poder llamar a las vistas adicionales "marcadores de posición", porque el inglés es mi segundo idioma. Será apreciado si alguien me puede decir eso.

CopperCash
fuente
Algo falta aquí. ¿Cómo se llama al bloque bajo la declaración de vistas ? No es un código legal de la forma en que lo pones
ishahak
0

Para aquellos que vinieron aquí para una Interface Buildersolución basada (Google me lleva aquí), simplemente agregue restricciones de ancho / alto constante, y seleccione la subvista -> arrastre de control a su supervista -> seleccione "centrar vertical / horizontalmente en la supervista".

superarts.org
fuente
0

Este es mi metodo

//create a 100*100 rect in the center of superView
var consts = NSLayoutConstraint.constraints(withVisualFormat: "H:|-space-[myView]-space-|", options: [], metrics:["space":view.bounds.width/2-50], views:["myView":myView])

consts += NSLayoutConstraint.constraints(withVisualFormat: "V:|-space-[myView]-space-|", options: [], metrics: ["space":view.bounds.height/2-50], views: ["myView":myView])
Calcifer
fuente
-1

Solo necesito decir que puedes usar esto en lugar de restricciones:

child.center = [parent convertPoint:parent.center fromView:parent.superview];
Nik Kov
fuente
44
Sólo hay que decir, que no es lo que está pidiendo ni va a funcionar si se gira el dispositivo, de jugar a pantalla cambia de tamaño, etc.
jeffjv
-1

Respuesta de @ igor-muzyka en Swift 2 :

NSLayoutConstraint.constraintsWithVisualFormat("H:[view]-(<=1)-[subview]", 
                                               options: .AlignAllCenterY,
                                               metrics: nil,
                                               views: ["view":view, "subview":subview])
Federico Zanetello
fuente
-3

En lugar de usar VFL, puede hacerlo fácilmente en el generador de interfaces. En el Storyboard principal, ctrl + clic en la vista que desea centrar. Arrastre a la supervista (mientras mantiene presionada la tecla Ctrl) y seleccione "Centro X" o "Centro Y". No se necesita código.

leenyburger
fuente