Autolayout de iOS con UIScrollview: ¿Por qué la vista de contenido de la vista de desplazamiento no llena la vista de desplazamiento?

El siguiente código (llamado en viewDidLoad) da como resultado una pantalla totalmente roja. Esperaría que fuera una pantalla completamente verde. ¿Por qué es rojo? ¿Y cómo puedo hacerlo todo verde?

UIScrollView* scrollView = [UIScrollView new]; scrollView.translatesAutoresizingMaskIntoConstraints = NO; scrollView.backgroundColor = [UIColor redColor]; [self.view addSubview:scrollView]; UIView* contentView = [UIView new]; contentView.translatesAutoresizingMaskIntoConstraints = NO; contentView.backgroundColor = [UIColor greenColor]; [scrollView addSubview:contentView]; NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView,contentView); [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]]; [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]]; [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]]; 

Las restricciones con vistas de desplazamiento funcionan de forma ligeramente diferente que con otras vistas. Las restricciones entre contentView y su superview ( scrollView ) son para el scrollView de scrollView , no para su frame . Esto puede parecer confuso, pero en realidad es bastante útil, lo que significa que nunca tendrá que ajustar el contentSize del contentSize , sino que el contentSize del contentSize se ajustará automáticamente para ajustarse a su contenido. Este comportamiento se describe en la Nota técnica TN2154 .

Si desea definir el tamaño de contentView en la pantalla o algo así, debería agregar una restricción entre contentView y la vista principal, por ejemplo. Eso es, sin duda, una antítesis de poner contenido en la vista de desplazamiento, por lo que probablemente no aconsejaría eso, pero se puede hacer.


Para ilustrar este concepto, que el tamaño de contentView estará determinado por su contenido, no por los bounds de scrollView , agregue una etiqueta a su contentView :

 UIScrollView* scrollView = [UIScrollView new]; scrollView.translatesAutoresizingMaskIntoConstraints = NO; scrollView.backgroundColor = [UIColor redColor]; [self.view addSubview:scrollView]; UIView* contentView = [UIView new]; contentView.translatesAutoresizingMaskIntoConstraints = NO; contentView.backgroundColor = [UIColor greenColor]; [scrollView addSubview:contentView]; UILabel *randomLabel = [[UILabel alloc] init]; randomLabel.text = @"this is a test"; randomLabel.translatesAutoresizingMaskIntoConstraints = NO; randomLabel.backgroundColor = [UIColor clearColor]; [contentView addSubview:randomLabel]; NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel); [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]]; [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]]; [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]]; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]]; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]]; 

Ahora verá que contentView (y, por lo tanto, el contentSize del contentSize de scrollView ) se ajustan para ajustarse a la etiqueta con los márgenes estándar. Y debido a que no especifiqué el ancho / alto de la etiqueta, eso se ajustará en función del texto que coloque en esa etiqueta.


Si desea que contentView también se ajuste al ancho de la vista principal, puede redefinir viewDict como tal y luego agregar estas restricciones adicionales (además de todas las demás, arriba):

 UIView *mainView = self.view; NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel, mainView); [mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[contentView(==mainView)]" options:0 metrics:0 views:viewDict]]; 

Existe un problema conocido (¿error?) Con tags multilínea en scrollviews, que si desea que cambie el tamaño de acuerdo con la cantidad de texto, debe realizar algunos juegos de manos, como:

 dispatch_async(dispatch_get_main_queue(), ^{ randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width; }); 

Intenté que la respuesta de Rob se vea bien al principio. ¡PERO! si también habilitó el zoom , este código de Autolayout se interpondrá en el camino. Sigue cambiando el tamaño de contentView cuando se utiliza el zoom. Es decir, si amplié (zoomScale> 1), no podré desplazar las partes fuera de la pantalla.

Después de días de pelear con Autolayout, lamentablemente no pude encontrar ninguna solución. Al final, ni siquiera importa (¿vale? Vale, lo siento). Al final, simplemente elimino Autolayout en contentView (uso un contenido de tamaño fijo), y luego en layoutSubviews, ajusto el self.scrollView.contentInset . (Estoy usando Xcode5, iOS 7)

Sé que esta no es una solución directa a esta pregunta. Pero solo quiero señalar una solución fácil. Funciona perfectamente para centrar un contentView de tamaño fijo en un scrollView, ¡con solo 2 líneas de código! Espero que pueda ayudar a alguien;)

 - (void)layoutSubviews { [super layoutSubviews]; // move contentView to center of scrollView by changing contentInset, // because I CANNOT set this with AUTOLAYOUT!!! CGFloat topInset = (self.frame.size.height - self.contentView.frame.size.height)/2; self.scrollView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0); } 

En general, Diseño automático considera que los bordes superior, izquierdo, inferior y derecho de una vista son los bordes visibles. Es decir, si fija una vista en el borde izquierdo de su supervista, realmente la fijará al valor x mínimo de los límites de la supervista. Cambiar el origen de los límites de la supervista no cambia la posición de la vista.

La clase UIScrollView desplaza su contenido cambiando el origen de sus límites. Para que esto funcione con Diseño automático, los bordes superior, izquierdo, inferior y derecho dentro de una vista de desplazamiento ahora significan los bordes de su vista de contenido.

Las restricciones en las subvistas de la vista de desplazamiento deben dar como resultado un tamaño para rellenar, que luego se interpreta como el tamaño de contenido de la vista de desplazamiento. (Esto no debe confundirse con el método intrinsicContentSize utilizado para Auto Layout). Para ajustar el tamaño del marco de la vista de desplazamiento con Diseño automático, las restricciones deben ser explícitas con respecto al ancho y alto de la vista de desplazamiento, o los bordes de la vista de desplazamiento deben estar vinculado a puntos de vista fuera de su subárbol.

Tenga en cuenta que puede hacer que una subvista de la vista de desplazamiento parezca flotar (no desplazarse) sobre el otro contenido de desplazamiento creando restricciones entre la vista y una vista fuera del subárbol de la vista de desplazamiento, como la supervista de la vista de desplazamiento.

Aquí hay dos ejemplos de cómo configurar la vista de desplazamiento, primero el enfoque mixto, y luego el enfoque puro