UIScrollView con pie de página adhesivo UIView y contenido de altura dynamic

¡Tiempo de desafío!

Imagina que tenemos 2 vistas de contenido:

  1. Vista UIV con contenido de altura dinámica (UITextView ampliable) = ROJO
  2. UIView como pie de página = AZUL

Este contenido está dentro de un UIScrollView = GEEN

¿Cómo debo estructurar y manejar las restricciones con el layout automático para archivar todos los casos siguientes?

Estoy pensando en la siguiente estructura básica para comenzar:

- UIScrollView (with always bounce vertically) - UIView - Container - UIView - DynamicHeightContent - UIView - Sticky Footer 

El event handlingl keyboard debe realizarse mediante las notifications de UIKeyboardWillShowNotification códigos UIKeyboardWillShowNotification y UIKeyboardWillHideNotification . Podemos elegir establecer la altura del marco final del keyboard en la restricción del pin inferior del contenedor UIView o en la interfaz inferior del contenido UIScrollView.

Ahora, la parte difícil es el pie de página adhesivo.

  • ¿Cómo nos aseguramos de que el pie de página adhesivo UIView se quede en la parte inferior si hay más pantallas disponibles que toda la vista de contenedor?
  • ¿Cómo sabemos el espacio disponible en la pantalla cuando se muestra / oculta el keyboard? seguramente lo necesitaremos.
  • ¿Es correcto esta estructura que propongo?

Gracias.

Recreación de casos

Cuando el contenido de text de UITextView es relativamente corto, las subvistas de la vista de contenido (es decir, la vista de text y el pie de página) no podrán dictar el tamaño de su vista de contenido a través de restricciones. Esto se debe a que cuando el contenido del text es corto, el tamaño de la vista de contenido debe determinar el tamaño de la vista de desplazamiento.

Actualización: el último párrafo es falso. Puede instalar una restricción de altura fija en la vista de contenido o en alguna parte de la jerarquía de vista de la vista de contenido. La constante de la restricción de altura fija se puede establecer en el código para reflejar la altura de la vista de desplazamiento. El último párrafo también refleja una falacia en el pensamiento. En un enfoque de layout automático puro, las subvistas de la vista de contenido no necesitan dictar el contentSize la vista de desplazamiento; en su lugar, es la vista de contenido en sí misma la que, en última instancia, debe dictar contentSize .

Independientemente, decidí ir con el llamado "enfoque mixto" de Apple para usar el layout automático con UIScrollView (consulte la Nota técnica de Apple: https://developer.apple.com/library/ios/technotes/tn2154/_index.html )

Algunos networkingactores técnicos de iOS, como Erica Sadun, prefieren usar el enfoque mixto en casi todas las situaciones ("iOS Auto Layout Demystified", 2da Ed.).

En el enfoque mixto, el marco de la vista de contenido y el tamaño de contenido de la vista de desplazamiento se establecen explícitamente en el código.

Aquí está el repository GitHub que creé para este desafío: https://github.com/bilobatum/StickyFooterAutoLayoutChallenge . Es una solución funcional completa con animation de cambios de layout. Funciona en dispositivos de diferentes tamaños. Para simplificar, deshabilité la rotation al paisaje.

Para aquellos que no quieren download y ejecutar el proyecto GitHub, he incluido algunos puntos destacados a continuación (para la implementación completa, tendrás que mirar el proyecto GitHub):

introduzca la descripción de la imagen aquíintroduzca la descripción de la imagen aquí

introduzca la descripción de la imagen aquíintroduzca la descripción de la imagen aquí

introduzca la descripción de la imagen aquí

La vista de contenido es naranja, la vista de text es gris y el pie de página adhesivo es azul. El text está visible detrás de la barra de estado mientras se desplaza. Realmente no me gusta, pero está bien para una demostración.

La única vista instanciada en storyboard es la vista de desplazamiento, que es de pantalla completa (es decir, barra de estado de solapamientos).

Para propósitos de testing, adjunto un reconocedor de gestos de doble toque al pie de página azul con el propósito de descartar el keyboard.

 - (void)viewDidLoad { [super viewDidLoad]; self.scrollView.alwaysBounceVertical = YES; [self.scrollView addSubview:self.contentView]; [self.contentView addSubview:self.textView]; [self.contentView addSubview:self.stickyFooterView]; [self configureConstraintsForContentViewSubviews]; // Apple's mixed (aka hybrid) approach to laying out a scroll view with Auto Layout: explicitly set content view's frame and scroll view's contentSize (see Apple's Technical Note TN2154: https://developer.apple.com/library/ios/technotes/tn2154/_index.html) CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text]; CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:textViewHeight]; // scroll view is fullscreen in storyboard; ie, it's final on-screen geometries will be the same as the view controller's main view; unfortunately, the scroll view's final on-screen geometries are not available in viewDidLoad CGSize scrollViewSize = self.view.bounds.size; if (contentViewHeight < scrollViewSize.height) { self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, scrollViewSize.height); } else { self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight); } self.scrollView.contentSize = self.contentView.bounds.size; } - (void)configureConstraintsForContentViewSubviews { assert(_textView && _stickyFooterView); // for debugging // note: there is no constraint between the subviews along the vertical axis; the amount of vertical space between the subviews is determined by the content view's height NSString *format = @"H:|-(space)-[textView]-(space)-|"; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(SIDE_MARGIN)} views:@{@"textView": _textView}]]; format = @"H:|-(space)-[footer]-(space)-|"; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(SIDE_MARGIN)} views:@{@"footer": _stickyFooterView}]]; format = @"V:|-(space)-[textView]"; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(TOP_MARGIN)} views:@{@"textView": _textView}]]; format = @"V:[footer(height)]-(space)-|"; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:@{@"space": @(BOTTOM_MARGIN), @"height": @(FOOTER_HEIGHT)} views:@{@"footer": _stickyFooterView}]]; // a UITextView does not have an intrinsic content size; will need to install an explicit height constraint based on the size of the text; when the text is modified, this height constraint's constant will need to be updated CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text]; self.textViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self.textView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:textViewHeight]; [self.textView addConstraint:self.textViewHeightConstraint]; } - (void)keyboardUp:(NSNotification *)notification { // when the keyboard appears, extraneous vertical space between the subviews is eliminated–if necessary; ie, vertical space between the subviews is networkinguced to the minimum if this space is not already at the minimum NSDictionary *info = [notification userInfo]; CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; keyboardRect = [self.view convertRect:keyboardRect fromView:nil]; double duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:self.textView.bounds.size.height]; CGSize scrollViewSize = self.scrollView.bounds.size; [UIView animateWithDuration:duration animations:^{ self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight); self.scrollView.contentSize = self.contentView.bounds.size; UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, keyboardRect.size.height, 0); self.scrollView.contentInset = insets; self.scrollView.scrollIndicatorInsets = insets; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { [self scrollToCaret]; }]; } 

Aunque el componente de layout automático de esta aplicación de demostración tomó algo de time, pasé casi tanto time en desplazar los problemas relacionados con un UITextView se anida dentro de un UIScrollView .

En lugar de utilizar un UIScrollView es muy probable que esté mejor con una UITableView . También podría ser mejor no usar el layout automático. Al less, he encontrado mejor no usarlo para este tipo de manipulaciones.

Mira en lo siguiente:

  • UITextView textViewDidChange
    • Cambie el tamaño de la vista de text utilizando sizeThatFits (ancho límite y uso de FLT_MAX para la altura). Cambia el marco, no el contenido Size.
    • Llame a UITableView beginUpdates / endUpdates para actualizar la vista de tabla
    • Desplácese al cursor
  • UIKeyboardWillShowNotification notification
    • En NSNotification que aparece, puede invocar userInfo (un dictionary) y la key UIKeyboardFrameBeginUserInfoKey . Reduzca el marco de la vista de la tabla en function de la altura del tamaño del keyboard.
    • Desplácese al cursor nuevamente (ya que los layouts cambiarán todos)
  • UIKeyboardWillHideNotification notification
    • Lo mismo que la notificación de la presentación, justo enfrente (aumentando la altura de la vista de la tabla)

Para que la vista del pie de página se adhiera a la parte inferior, puede agregar una celda intermedia a la vista de la tabla y hacer que cambie de tamaño según el tamaño del text y si el keyboard está visible.

Lo anterior definitivamente requerirá una manipulación adicional de su parte: no entiendo completamente todos sus casos, pero definitivamente debería comenzar.

Si entiendo una tarea completa, mi solución se coloca en vistas "rojas" y "azules" en una vista de contenedor, y en el momento en que conoce el tamaño del contenido dynamic (rojo) puede calcular el tamaño del contenedor y establecer el tamaño de contenido de scrollView. Más tarde, en events de keyboard, puede ajustar el espacio en blanco entre las vistas de contenido y pie de página