Diseño automático UIScrollView con subvistas con alturas dinámicas

Estoy teniendo problemas con UIScrollView usando restricciones de diseño automático. Tengo la siguiente jerarquía de vista, con restricciones establecidas a través de IB:

- ScrollView (leading, trailing, bottom and top spaces to superview) -- ContainerView (leading, trailing, bottom and top spaces to superview) --- ViewA (full width, top of superview) --- ViewB (full width, below ViewA) --- Button (full width, below ViewB) 

ViewA y ViewB tienen alturas iniciales de 200 puntos, pero pueden invertirse verticalmente hasta una altura de 400 puntos haciendo clic en él. ViewA y ViewB se expanden al actualizar su restricción de altura (de 200 a 400). Aquí está el fragmento correspondiente:

 if(self.contentVisible) { heightConstraint.constant -= ContentHeight; // + additional View's internal constraints update to hide additional content self.contentVisible = NO; } else { heightConstraint.constant += ContentHeight; // + additional View's internal constraints update to show additional content self.contentVisible = YES; } [self.view setNeedsUpdateConstraints]; [UIView animateWithDuration:.25f animations:^{ [self.view layoutIfNeeded]; }]; 

Mi problema es que si ambas vistas se expanden, necesito poder desplazarme para ver todo el contenido y, en este momento, el desplazamiento no funciona. ¿Cómo puedo actualizar la vista de desplazamiento usando restricciones para reflejar los cambios de las alturas de ViewA y ViewB?

La única solución que puedo pensar hasta ahora es establecer manualmente la altura de ContainerView después de la animación, que será la sum de las alturas del botón ViewA + ViewB +. Pero creo que hay una mejor solución?

Gracias

Utilizo una estructura pura como la siguiente

 -view -scrollView -view A -view B -Button 

Asegúrese de que el botón ( LA ÚLTIMA vista ) tiene una restricción (espaciado vertical desde su parte inferior a la supervista, que es la vista de desplazamiento), en este caso, independientemente de los cambios para su vista A y la vista B, la altura de la vista de desplazamiento se modificará en consecuencia .

Hago referencia a este gran sitio de libros en línea.

Simplemente lea la sección “Crear una vista de desplazamiento”, debe tener una idea.

Tuve el problema similar de que estaba creando una vista de detalle y el uso de Interface Builder con diseño automático es una buena opción para la tarea.

¡Buena suerte!

(Recursos adicionales:

Astack debate de desbordamiento sobre el diseño automático para la vista de desplazamiento .

iOS 6 tiene una Notas de versión que habla sobre el soporte de Auto Layout para UIScrollView.

Libre explicación en línea del libro de iOS sobre la vista de desplazamiento. ¡Esto realmente me ayudó mucho!

Digamos que tenemos una jerarquía como esta (Label1 es una subvista de ContentView; ContentView es una subvista de ScrollView, ScrollView es una subvista de la vista del viewcontroller):

ViewController's View ScrollView ContentView Label1 Label2 Label3

ScrollView está restringido con autolayout de la manera normal a la vista del viewcontroller.

ContentView está anclado arriba / izquierda / derecha / abajo a scrollview. Lo que significa que tiene restricciones que hacen que los bordes superior / inferior / inicial / posterior de ContentView estén limitados a los mismos bordes en ScrollView. Aquí hay una clave: estas restricciones son para el tamaño de contenido del ScrollView, no su tamaño de marco como se muestra en la vista del controlador de vista. Por lo tanto, no indica que ContentView tenga el mismo tamaño de marco que ScrollView, sino que Scrollview dice que ContentView es su contenido y, por lo tanto, si contentview es más grande que ScrollView, se puede desplazar, al igual que con scrollView.contentSize larger que scrollView.frame hace que el contenido sea desplazable.

Aquí hay otra clave: ahora debe tener suficientes restricciones entre ContentView, Label1-3 y cualquier otra cosa además de la vista de desplazamiento para que ContentView pueda calcular su ancho y altura a partir de esas restricciones.

Por ejemplo, si desea un conjunto de tags que se desplazan verticalmente, establece una restricción para que el ancho de ContentView sea igual al ancho de ViewController View, que se ocupa del ancho. Para cuidar la altura, coloca Label1 arriba a ContentView arriba, Label2 arriba a Label1 abajo, Label3 arriba a Label2 abajo y finalmente (y más importante) fija la parte inferior de Label3 al fondo de ContentView. Ahora tiene suficiente información para calcular la altura de ContentView.

Espero que esto le dé una pista a alguien, ya que leí las publicaciones anteriores y todavía no pude entender cómo hacer las restricciones de ancho y alto de ContentView correctamente. Lo que me faltaba era fijar la parte inferior de Label3 en la parte inferior de ContentView, de lo contrario, ¿cómo podría ContentView saber qué altura tiene (como Label3 sería flotante y no habría ninguna restricción para decirle a ContentView dónde está la posición inferior).

Este es un ejemplo de cómo he dispuesto un UIScrollView de autolayout puro con una vista de contenedor. Lo he comentado para hacerlo más claro:

contenedor es una UIView estándar y el cuerpo es una UITextView

 - (void)viewDidLoad { [super viewDidLoad]; //add scrollview [self.view addSubview:self.scrollView]; //add container view [self.scrollView addSubview:self.container]; //body as subview of container (body size is undetermined) [self.container addSubview:self.body]; NSDictionary *views = @{@"scrollView" : self.scrollView, @"container" : self.container, @"body" : self.body}; NSDictionary *metrics = @{@"margin" : @(100)}; //constrain scrollview to superview, pin all edges [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:kNilOptions metrics:metrics views:views]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:kNilOptions metrics:metrics views:views]]; //pin all edges of the container view to the scrollview (i've given it a horizonal margin as well for my purposes) [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[container]|" options:kNilOptions metrics:metrics views:views]]; [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[container]-margin-|" options:kNilOptions metrics:metrics views:views]]; //the container view must have a defined width OR height, here i am constraining it to the frame size of the scrollview, not its bounds //the calculation for constant is so that it's the width of the scrollview minus the margin * 2 [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:-([metrics[@"margin"] floatValue] * 2)]]; //now as the body grows vertically it will force the container to grow because it's trailing edge is pinned to the container's bottom edge //it won't grow the width because the container's width is constrained to the scrollview's frame width [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[body]|" options:kNilOptions metrics:metrics views:views]]; [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[body]|" options:kNilOptions metrics:metrics views:views]]; } 

En mi ejemplo, ‘cuerpo’ es una UITextView, pero podría ser cualquier otra cosa. Si está utilizando una UITextView, tenga en cuenta que para que crezca verticalmente debe tener una restricción de altura que se establece en viewDidLayoutSubviews. Añada la siguiente restricción en viewDidLoad y consérvela como referencia:

 self.bodyHeightConstraint = [NSLayoutConstraint constraintWithItem:self.body attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0f constant:100.0f]; [self.container addConstraint:self.bodyHeightConstraint]; 

Luego en viewDidLayoutSubviews calcula la altura y actualiza la constante de la restricción:

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self.bodyHeightConstraint setConstant:[self.body sizeThatFits:CGSizeMake(self.container.width, CGFLOAT_MAX)].height]; [self.view layoutIfNeeded]; } 

El segundo pase de disposición es necesario para cambiar el tamaño del UITextView.

Usa este código ScrollView setContentSize debe llamarse async en el hilo principal.

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; dispatch_async(dispatch_get_main_queue(), ^ { CGRect contentRect = CGRectZero; for (UIView *view in scrollView.subviews) if (!view.hidden) contentRect = CGRectUnion(contentRect, view.frame); contentRect.size.height=contentRect.size.height; scrollView.contentSize = contentRect.size; }); } 

En todo momento, la vista de desplazamiento debería conocer su tamaño de contenido. El tamaño del contenido se deduce de las subvistas de la vista de desplazamiento. Es muy útil asignar las propiedades del controlador a las restricciones en el archivo xib que describe las alturas de las subvistas. Luego, en el código (un bloque de animación) puede simplemente cambiar las constantes de estas propiedades de restricción. Si necesita cambiar toda la restricción, conserve una referencia para que pueda actualizarla más tarde en el contenedor primario.

Mi variante para la vista de desplazamiento con! ¡ Dynamic ! altura:

1) Agregue scroll view a su UIView . Pin todas las restricciones (arriba, abajo, plomo, camino).

2) Agregue UIView a la Scroll View . Pin todas las restricciones (arriba, abajo, plomo, camino). Será su Content view . También puedes cambiarle el nombre.

3) Control de arrastre desde la Content view de Scroll viewEqual width

4) Agregue contenido a su UIView . Establecer las restricciones necesarias. ¡Y! En el elemento inferior, agregue la restricción inferior NO Greater or equal ( >= ) (Como la mayoría de las personas habla) ¡ PERO Equal ! Ponlo en 20 por ejemplo.

En mi situación, tengo UIImageView en el contenido. He conectado su height al código. Y si cambio a 1000 , el desplazamiento es visible. Y todo funciona

Funciona como un encanto para mí. Cualquier pregunta, bienvenido a los comentarios.