ios - El contenido cae debajo de la barra de navegación cuando está incrustado en el controlador de vista de contenedor personalizado.
objective-c uinavigationbar (5)
El controlador de vista de contenedor personalizado deberá ajustar el contentInset
de contenido del segundo controlador de vista de acuerdo con la altura de la barra de navegación conocida, respetando la propiedadadjustsScrollViewInsets automaticallyAdjustsScrollViewInsets
del controlador de vista hijo. (Puede que también le interese la propiedad topLayoutGuide
de su contenedor: asegúrese de que devuelve el valor correcto durante y después del cambio de vista).
UIKit es notablemente inconsistente (y problemático) en cómo aplica esta lógica; a veces verá que realiza este ajuste automáticamente alcanzando múltiples controladores de vista en la jerarquía, pero a menudo después de un cambio de contenedor personalizado deberá hacer el trabajo usted mismo.
ACTUALIZAR
En función de la respuesta de Tim, implementé lo siguiente en cada controlador de vista que tenía una vista de desplazamiento (o subclase) que formaba parte de mi contenedor personalizado:
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (parent) {
CGFloat top = parent.topLayoutGuide.length;
CGFloat bottom = parent.bottomLayoutGuide.length;
// this is the most important part here, because the first view controller added
// never had the layout issue, it was always the second. if we applied these
// edge insets to the first view controller, then it would lay out incorrectly.
// first detect if it''s laid out correctly with the following condition, and if
// not, manually make the adjustments since it seems like UIKit is failing to do so
if (self.collectionView.contentInset.top != top) {
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
self.collectionView.scrollIndicatorInsets = newInsets;
}
}
[super didMoveToParentViewController:parent];
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tengo un controlador de vista contenedor personalizado llamado SegmentedPageViewController
. Configuré esto como un UINavigationController''s rootViewController
.
El objetivo de SegmentedPageViewController
es permitir que UISegmentedControl
, establecido como titleView de NavController, cambie entre diferentes controladores de vista secundarios.
Estos controladores de vista secundarios contienen una vista de desplazamiento, una vista de tabla o una vista de colección.
Estamos descubriendo que el primer controlador de vista se carga correctamente, colocado correctamente debajo de la barra de navegación. Pero cuando cambiamos a un nuevo controlador de vista, la barra de navegación no se respeta y la vista se establece debajo de la barra de navegación.
Estamos utilizando el diseño automático y el generador de interfaz. Hemos intentado todo lo que podemos pensar, pero no podemos encontrar una solución consistente.
Aquí está el bloque de código principal responsable de configurar el primer controlador de visualización y cambiar a otro cuando un usuario toca el control segmentado:
- (void)switchFromViewController:(UIViewController *)oldVC toViewController:(UIViewController *)newVC
{
if (newVC == oldVC) return;
// Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (newVC) {
// Set the new view controller frame (in this case to be the size of the available screen bounds)
// Calulate any other frame animations here (e.g. for the oldVC)
newVC.view.frame = self.view.bounds;
// Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (oldVC) {
// **** THIS RUNS WHEN A NEW VC IS SET ****
// DIFFERENT FROM FIRST VC IN THAT WE TRANSITION INSTEAD OF JUST SETTING
// Start both the view controller transitions
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Swap the view controllers
// No frame animations in this code but these would go in the animations block
[self transitionFromViewController:oldVC
toViewController:newVC
duration:0.25
options:UIViewAnimationOptionLayoutSubviews
animations:^{}
completion:^(BOOL finished) {
// Finish both the view controller transitions
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}];
} else {
// **** THIS RUNS WHEN THE FIRST VC IS SET ****
// JUST STANDARD VIEW CONTROLLER CONTAINMENT
// Otherwise we are adding a view controller for the first time
// Start the view controller transition
[self addChildViewController:newVC];
// Add the new view controller view to the view hierarchy
[self.view addSubview:newVC.view];
// End the view controller transition
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}
}
}
Encontré una solución mejor, uso el método no documentado de UINavigationController.
#import <UIKit/UIKit.h>
@interface UINavigationController (ContentInset)
- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller;
@end
#import "UINavigationController+ContentInset.h"
@interface UINavigationController()
- (void)_computeAndApplyScrollContentInsetDeltaForViewController:(id)arg1;
@end
@implementation UINavigationController (ContentInset)
- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller
{
if ([UINavigationController instancesRespondToSelector:@selector(_computeAndApplyScrollContentInsetDeltaForViewController:)])
[self _computeAndApplyScrollContentInsetDeltaForViewController:controller];
}
@end
entonces, haz como esto
- (void) cycleFromViewController: (UIViewController*) oldC
toViewController: (UIViewController*) newC
{
[oldC willMoveToParentViewController:nil];
[self addChildViewController:newC];
[self transitionFromViewController: oldC toViewController: newC
duration: 0.25 options:0
animations:^{
newC.view.frame = oldC.view.frame;
[self.navigationController computeAndApplyScrollContentInsetDeltaForViewController:newC];
}
completion:^(BOOL finished) {
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}];
}
Establecer edgesForExtendedLayout = []
en mis controladores secundarios funcionó para mí.
Esto parece ser mucho más simple que lo que la gente entiende.
UINavigationController configurará las inserciones de la vista de desplazamiento solo mientras se preparan las subvistas. addChildViewController:
no causa un diseño, sin embargo, luego de llamarlo, solo necesita llamar a setNeedsLayout
en su navigationController. Esto es lo que hago al cambiar de vista en una vista personalizada con pestañas:
[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];
La última línea provocará que las inserciones de la vista de desplazamiento se vuelvan a calcular para el contenido del nuevo controlador de vista.
FYI en caso de que alguien tenga un problema similar: este problema puede ocurrir incluso sin controladores de vista incrustados. Parece que automaticallyAdjustsScrollViewInsets
solo se aplica si su scrollview (o tableview / collectionview / webview) es la primera vista en la jerarquía de su controlador de vista.
A menudo agrego un UIImageView primero en mi jerarquía para tener una imagen de fondo. Si hace esto, debe configurar manualmente las inserciones de borde de la vista de desplazamiento en viewDidLayoutSubviews:
- (void) viewDidLayoutSubviews {
CGFloat top = self.topLayoutGuide.length;
CGFloat bottom = self.bottomLayoutGuide.length;
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
}