¿Cómo puedo obtener el ancho y alto actuales de una vista cuando utilizo restricciones de diseño automático?

108

No estoy hablando de la propiedad del marco, porque a partir de eso solo puede obtener el tamaño de la vista en el xib. Estoy hablando de cuándo se cambia el tamaño de la vista debido a sus limitaciones (tal vez después de una rotación o en respuesta a un evento). ¿Hay alguna forma de obtener su ancho y alto actuales?

Intenté iterar a través de sus restricciones buscando restricciones de ancho y alto, pero eso no es muy limpio y falla cuando hay restricciones intrínsecas (ya que no puedo diferenciar entre las dos). Además, eso solo funciona si realmente tienen restricciones de ancho y alto, lo que no es así si dependen de otras restricciones para cambiar el tamaño.

¿Por qué esto es tan difícil para mí? ARG!

yuf
fuente
Hubiera pensado que los límites de la vista en cualquier momento en particular mantuvieron su ancho y alto actuales. Eso es lo que se ajusta durante el proceso de diseño.
Phillip Mills
Hmm, nunca pensé en verificar los límites. Lo haré ahora.
yuf

Respuestas:

246

La respuesta es [view layoutIfNeeded].

Este es el por qué:

Aún obtiene el ancho y alto actuales de la vista al inspeccionar view.bounds.size.widthy view.bounds.size.height(o el marco, que es equivalente a menos que esté jugando con view.transform).

Si lo que desea es el ancho y la altura implícitos en sus restricciones existentes, la respuesta no es inspeccionar las restricciones manualmente, ya que eso requeriría que vuelva a implementar toda la lógica de resolución de restricciones del sistema de diseño automático para interpretarlas. limitaciones. En su lugar, lo que debe hacer es simplemente pedirle a auto layout que actualice ese diseño , de modo que resuelva las restricciones y actualice el valor de view.bounds con la solución correcta, y luego inspeccione view.bounds.

¿Cómo le pides al diseño automático que actualice el diseño? Llame [view setNeedsLayout]si desea que el diseño automático actualice el diseño en el siguiente giro del ciclo de ejecución.

Sin embargo, si desea que actualice el diseño de inmediato, para que pueda acceder inmediatamente al nuevo valor de límites más adelante dentro de su función actual, o en otro punto antes del turno del ciclo de ejecución, entonces debe llamar [view setNeedsLayout]y [view layoutIfNeeded].

Hiciste una segunda pregunta: "¿cómo puedo cambiar una restricción de altura / ancho si no tengo una referencia a ella directamente?".

Si crea la restricción en IB, la mejor solución es crear un IBOutlet en su controlador de vista o en su vista para que tenga una referencia directa a él. Si creó la restricción en el código, entonces debe mantener una referencia en una propiedad débil interna en el momento en que la creó. Si alguien más creó la restricción, entonces debe encontrarla examinando la propiedad view.constraints en la vista, y posiblemente toda la jerarquía de la vista, e implementando la lógica que encuentra la NSLayoutConstraint crucial. Este es probablemente el camino equivocado, ya que también requiere que usted determine qué restricción particular determinó el tamaño de los límites, cuando no se garantiza que haya una respuesta simple a esa pregunta. El valor de los límites finales podría ser la solución a un sistema muy complicado de restricciones múltiples,

alga
fuente
16
Una excelente respuesta y también bien explicada. Me salvó después de una hora o dos de tirarme del pelo.
Ben Kreeger
2
layoutIfNeededes genial y hará que el marco esté disponible de inmediato. Sin embargo, también forzará la representación de las restricciones en todas las vistas en los subárboles de la vista en la que se llama. Si está agregando vistas mediante programación, por ejemplo, y las llama layoutIfNeededa todas en su rutina recursiva, puede encontrar que su jerarquía de vistas se representa muy lentamente. (Aprendí esto de la manera difícil) Como se mencionó en la excelente respuesta, 'setNeedsLayout` es más eficiente y hará que el marco esté disponible en la siguiente pasada de diseño, lo que idealmente ocurre en aproximadamente 1/60 de segundo.
shmim
1
Sé que esto es antiguo, pero ¿dónde se llama a este método? En initWithCoder?
MayNotBe
4
¿Por qué forzar el diseño solo para obtener los límites? Esto parece ineficaz y, con una interfaz compleja, será potencialmente costoso. En su lugar, acceda a los límites más recientes desde viewDidLayoutSubviews o incluso viewWillLayoutSubviews y deje que Cocoa maneje su propio tiempo de diseño. Establezca una propiedad si necesita acceder al valor o la marca para evitar múltiples llamadas si eso es un problema.
smileBot
1
Sí, solo necesita forzar el diseño si necesita acceder al valor calculado por el diseño automático antes del giro del ciclo de ejecución, por ejemplo, dentro de la extensión de la llamada de función actual.
alga
8

Tuve un problema similar en el que necesitaba agregar un borde superior e inferior a un UITableViewtamaño que cambia según su configuración de restricciones en UIStoryboard. Pude acceder a las restricciones actualizadas con - (void)viewDidLayoutSubviews. Esto es útil para que no necesite crear una subclase de una vista y anular su método de diseño.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Sin llamar al método desde el viewDidLayoutSubviewmétodo, solo el borde superior se dibuja correctamente, ya que el borde inferior está en algún lugar fuera de la pantalla.

Brandon Leer
fuente
3
viewDidLayoutSubviews se llama varias veces, está creando toneladas de capas ... Debe agregar algunas comprobaciones de existencia antes de agregar otra capa.
Kalzem
Comenta con algunos años de retraso ... también deberías llamar a este método en la superclase al anular antes que nada:[super viewDidLayoutSubviews];
Alejandro Iván
5

Para aquellos que todavía pueden estar enfrentando tales problemas, especialmente con TableviewCell.

Simplemente anule el método:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

En el caso de UITableViewCell o UICollectionViewCell, cree una subclase de la celda y anule el mismo método:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
fuente
esta es la única forma en que podría obtener el tamaño final real de mi vista
Benoit Jadinon
Esta fue la única forma en que pude obtener el tamaño final para cualquiera de mis vistas restringidas porque antes estaba tratando de ponerlas en awakeFromNib, lo que no dio tiempo a las subvistas para cambiar el tamaño primero. ¡Gracias!
Azin Mehrnoosh
3

Usar -(void)viewWillAppear:(BOOL)animatedy llamar [self.view layoutIfNeeded];- Funciona, lo he probado.

porque si lo usa -(void)viewDidLayoutSubviews, definitivamente funcionará, pero este método se llama cada vez que su interfaz de usuario exige actualizaciones / cambios. Lo cual será difícil de manejar. La tecla programable es que usa una variable bool para evitar ese bucle de llamadas. mejor uso viewWillAppear. viewWillAppearTambién se llamará a Remember si la vista se vuelve a cargar (sin reasignar).


fuente
2

El marco sigue siendo válido. Al final, la vista usa su propiedad de marco para distribuirse. Calcula ese marco en función de todas las restricciones. Las restricciones solo se utilizan para el diseño inicial (y en cualquier momento se llama a layoutSubviews en una vista como después de una rotación). Después de eso, la información de la posición está en la propiedad del marco. ¿O estás viendo lo contrario?

borrrden
fuente
1
Estoy viendo lo contrario. Los valores del marco no cambian, incluso después de cambiar las restricciones de ancho / alto directamente (cambiando sus constantes. Tengo un IBOutlet para ellos en el xib)
yuf
Mi problema es único porque cambio la restricción, luego necesito usar el marco en la misma función. Llamar a setNeedsLayout no lo actualiza de inmediato. Supongo que primero necesito esperar el diseño y luego usar el marco. Mi segunda pregunta es, ¿cómo puedo cambiar una restricción de altura / ancho si no tengo una referencia a ella directamente?
yuf
1
Debería dispatch_async entonces, y le permitirá retrasar su código hasta el siguiente marco. En cuanto a la segunda parte ... no sé ... tendrías que iterar todas las restricciones y buscar cuál es aplicable ... IBOutlets son mucho más fáciles.
borrrden
Es cierto para IBOutlets, pero quiero escribir una función en una categoría para una UIView con la que no tendré IB para trabajar.
yuf