Creación de una vista de métricas basada en la reproducción automática

Tengo una vista reutilizable que UITableViewCell's en UITableViewCell's y UICollectionViewCell 's, y necesito get sus dimensiones para tableView:heightForRowAtIndexPath: Algunas subvenciones tienen cosas dentro de layoutSubviews para que no pueda llamar a systemLayoutForContentSize: cambio, mi plan es:

  1. Cree una instancia de la vista de métricas.
  2. Establezca el tamaño para include el ancho deseado.
  3. Llénelo con datos.
  4. Actualización de restricciones / Subvenciones de layout.
  5. Toma la altura de la vista o una vista interna de "tamaño".

El problema con el que me estoy topando es que no puedo forzar la vista al layout sin insertla en la vista y esperar el runloop.

He destilado un ejemplo bastante aburrido. Aquí está View.xib. La subvista está desalineada para resaltar que la vista nunca se distribuye incluso a la position de reference:

View.xib

En el hilo principal llamo:

 UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0]; NSLog(@"Subviews: %@", view.subviews); view.frame = CGRectMake(0, 0, 200, 200); [view updateConstraints]; [view layoutSubviews]; NSLog(@"Subviews: %@", view.subviews); [self.view addSubview:view]; [view updateConstraints]; [view layoutSubviews]; NSLog(@"Subviews: %@", view.subviews); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Subviews: %@", view.subviews); }); 

Obtengo la siguiente información de vista:

 1) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>" 2) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>" 3) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>" 4) "<UIView: 0x8bad9e0; frame = (0 100; 100 100); autoresize = W+H; layer = <CALayer: 0x8be0070>>" 

1 indica que la vista de recién salido de la NIB no se ha distribuido. 2 indica que updateConstraints/layoutSubviews no hizo nada. 3 indica que agregarlo a la jerarquía de la vista no hizo nada. 4 finalmente indica que agregando a la jerarquía de la vista y una pasa a través del bucle principal, expuso la vista.

Me gustaría llegar al punto donde puedo get las dimensiones de la vista sin tener que dejar que la aplicación lo maneje o realizar cálculos manuales (string height + constraint1 + constraint2) por mi count.

Actualizar

He observado que si UIWindow view dentro de una UIWindow obtendré una ligera mejora:

 UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0]; UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; view.frame = CGRectMake(0, 0, 200, 200); [window addSubview:view]; [view layoutSubviews]; 

Si view.translatesAutoresizingMaskIntoConstraints == YES , se mostrarán las subvistas inmediatas de la vista, pero ninguno de sus hijos.

La pregunta de reproducción automática

En el caso básico que mencionaste, puedes get el tamaño correcto llamando a setNeedsLayout y luego a layoutIfNeeded en la vista de contenedor.

De la reference de la class layoutIfNeeded en layoutIfNeeded :

Use este método para forzar el layout de las subvistas antes del dibujo. Comenzando por el receptor, este método atraviesa hacia arriba a través de la jerarquía de vistas, siempre que las vistas superventas requieran layout. Luego expone todo el tree debajo de ese antepasado. Por lo tanto, llamar a este método puede forzar potencialmente el layout de toda su jerarquía de vistas. La implementación de UIView de esto llama al método CALayer equivalente y, por lo tanto, tiene el mismo comportamiento que CALayer.

No creo que la "jerarquía de vista completa" se aplique a su caso de uso, ya que la vista de métricas presumiblemente no tendría una supervisión.

Código de muestra

En un proyecto vacío de muestra, con solo este código, el marco correcto se determina después de que se llama a layoutIfNeeded :

 #import "ViewController.h" @interface ViewController () @property (nonatomic, strong) UIView *networkingView; @end @implementation ViewController @synthesize networkingView; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. networkingView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 220, 468)]; networkingView.backgroundColor = [UIColor networkingColor]; networkingView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:networkingView]; NSLog(@"Red View frame: %@", NSStringFromCGRect(networkingView.frame)); // outputs "Red View frame: {{50, 50}, {220, 468}}" [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[networkingView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(networkingView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[networkingView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(networkingView)]]; NSLog(@"Red View frame: %@", NSStringFromCGRect(networkingView.frame)); // outputs "Red View frame: {{50, 50}, {220, 468}}" [self.view setNeedsLayout]; NSLog(@"Red View frame: %@", NSStringFromCGRect(networkingView.frame)); // outputs "Red View frame: {{50, 50}, {220, 468}}" [self.view layoutIfNeeded]; NSLog(@"Red View frame: %@", NSStringFromCGRect(networkingView.frame)); // outputs "Red View frame: {{0, 100}, {100, 100}}" } @end 

consideraciones adicionales

Ligeramente fuera del scope de su pregunta, he aquí algunos otros problemas que puede encontrar, ya que he trabajado en este problema exacto en una aplicación real:

  • Cálculo de esto en heightForRowAtIndexPath: puede ser costoso, por lo que es heightForRowAtIndexPath: precalcular y almacenar en caching los resultados
  • El precálculo debe hacerse en un hilo de background, pero el layout de UIView no funciona bien a less que se realice en el hilo principal
  • Definitivamente debería implementar estimatedHeightForRowAtIndexPath: para networkingucir el impacto de estos problemas de performance.

Uso de intrinsicContentSize

En respuesta a:

Algunas subvenciones tienen cosas dentro de layoutSubviews así que no puedo llamar a systemLayoutForContentSize:

Puede usar este método si implementa intrinsicContentSize , lo que permite que una vista sugiera un tamaño óptimo para sí mismo. Una implementación para esto podría ser:

 - (CGSize) intrinsicContentSize { [self layoutSubviews]; return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame)); } 

Este enfoque simple solo funcionará si el método layoutSubviews no hace reference a un tamaño ya establecido (como self.bounds o self.frame ). Si lo hace, es posible que tenga que hacer algo como:

 - (CGSize) intrinsicContentSize { self.frame = CGRectMake(0, 0, 10000, 10000); while ([self viewIsWayTooLarge] == YES) { self.frame = CGRectInset(self.frame, 100, 100); [self layoutSubviews]; } return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame)); } 

Obviamente, necesitará ajustar estos valores para que coincidan con el layout particular de cada vista, y es posible que necesite sintonizar el performance.

Finalmente, añadiré que, debido en parte al costo exponencialmente creciente de usar el layout automático , para todas las celdas de la tabla less las más simples, por lo general termino usando cálculos de altura manual.

Es probable que esté llamando al código de demostración cuando el controller de vista primero cargue su vista, como en viewDidLoad u otro método de ciclo de vida. Las geometrys de la subvista anidada no reflejarán sus restricciones hasta que se viewDidLayoutSubviews . Nada de lo que hagas durante el ciclo de vida inicial de un controller de vista hará que ese método llegue más rápido.

Actualización 12/30/13: Después de probar el código de muestra de Aaron Brager, ahora me doy count de que el párrafo anterior es incorrecto. Al parecer, puede forzar el layout en viewDidLoad llamando a setNeedsLayout seguido de layoutIfNeeded .

Si ejecutó el código de demostración en respuesta a un button, click su lugar, creo que verá las geometrys finales de su subvista anidada registradas antes de que se complete el método de acción.

 - (IBAction)buttonTapped:(id)sender { UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0]; view.frame = CGRectMake(0, 0, 200, 200); [self.view addSubview:view]; [self.view layoutIfNeeded]; NSLog(@"Subviews: %@", view.subviews); } 

En este último caso, puede solicitar el layout a pedido porque el controller de vista ha completado su configuration inicial.

Pero durante la configuration inicial de un controller de vista, ¿cómo vas a get las geometrys finales de tu subvista reutilizable?

Después de configurar el contenido para la subvista reutilizable, solicite a la controllera de vista que solicite a la subvista su tamaño. En otras palabras, implementa un método en tu vista personalizada que calcula el tamaño según el contenido.

Por ejemplo, si el contenido de la subview es una cadena atribuida, puede usar un método como boundingRectWithSize:options:context: para ayudar a determinar el tamaño de su subview.

 CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsersLineFragmentOrigin context:nil];