ios10 - La coloración de la barra de navegación en ViewWillAppear sucede demasiado tarde en iOS 10
(4)
Me enfrento a un error extraño, que solo ocurre en iOS 10.
Tengo una aplicación con varias pantallas, y cada pantalla colorea la viewWillAppear
navigationBar
en la vista viewWillAppear
. Así que cuando vayas a la siguiente pantalla, estará correctamente coloreada.
Sin embargo, al realizar pruebas en iOS 10, de repente veo el siguiente comportamiento cuando vuelvo a una pantalla anterior: cuando aparece la pantalla anterior, la navigationBar
aún tiene el color de la pantalla anterior y luego parpadea al color adecuado. Casi parece que viewWillAppear
se comporta de alguna manera como viewDidAppear
.
Código relevante:
ViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[ViewControllerPainter paint:self withBackground:[UIColor whiteColor] andForeground:[UIColor blackColor] andIsLight:true];
}
Pintor:
+ (void)paint:(UIViewController *)controller withBackground:(UIColor *)backgroundColor andForeground:(UIColor *)foregroundColor andIsLight:(bool)isLight
{
controller.navigationController.navigationBar.opaque = true;
controller.navigationController.navigationBar.translucent = false;
controller.navigationController.navigationBar.tintColor = foregroundColor;
controller.navigationController.navigationBar.barTintColor = backgroundColor;
controller.navigationController.navigationBar.backgroundColor = backgroundColor;
controller.navigationController.navigationBar.barStyle = isLight ? UIBarStyleDefault : UIBarStyleBlack;
controller.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: foregroundColor};
}
¿Es esto un error? ¿Hay algo que pueda hacer para arreglar esto? Es muy frustrante.
Esto es lo que cambió de acuerdo con las Notas de la versión de iOS 10 SDK :
En iOS 10, UIKit ha actualizado y unificado la gestión de fondo para UINavigationBar, UITabBar y UIToolbar. En particular, los cambios en las propiedades de fondo de estas vistas (como imágenes de fondo o de sombra, o configuración del estilo de barra) pueden iniciar un paso de diseño para que la barra resuelva la nueva apariencia de fondo.
En particular, esto significa que los intentos de cambiar el aspecto de fondo de estas barras dentro de - [UIView layoutSubviews], - [UIView updateConstraints], - [UIViewController willSayoutSubviews], - [UIViewController didLayoutSubviews] - - [UIViewController updateViewConstintsints, o cualquier otro El método que se llama en respuesta al diseño puede dar como resultado un bucle de diseño.
Entonces, el problema parece ser que viewWillAppear
está activando el ciclo de diseño mencionado, ya que se llama como resultado de un cambio de diseño:
La solución rápida para mí fue anular popViewControllerAnimated
y pushViewController
y actualizar el fondo de la UINavigationController
de navigationBar
en mi subclase de UINavigationController
. Así es como se ve:
override func popViewControllerAnimated(animated: Bool) -> UIViewController? {
let poppedViewController = super.popViewControllerAnimated(animated)
// Updates the navigation bar appearance
updateAppearanceForViewController(nextViewController)
return poppedViewController
}
override func pushViewController(viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
// Updates the navigation bar appearance
updateAppearanceForViewController(viewController)
}
Mi conjetura es que funciona porque el popViewControllerAnimated
pushViewController
no llama a popViewControllerAnimated
y pushViewController
como resultado de un cambio de diseño, sino a un evento táctil. Así que tenlo en cuenta si quieres encontrar otro lugar para actualizar el fondo de tu navigationBar
.
Estoy publicando la solución para Objective-C (subclase de UINavigationController):
#import "FUINavigationController.h"
@interface FUINavigationController ()
@end
@implementation FUINavigationController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"current: %@",[self.topViewController class]);
if ([NSStringFromClass([self.topViewController class]) isEqualToString:@"LoginVC"]) {
[self setNavBarHidden];
}
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UIViewController*)popViewControllerAnimated:(BOOL)animated {
UIViewController *popedVC = [super popViewControllerAnimated:animated];
if ([NSStringFromClass([popedVC class]) isEqualToString:@"SignUpVC"] && [NSStringFromClass([[super topViewController] class]) isEqualToString:@"LoginVC"]) {
[self setNavBarHidden];
}
return popedVC;
}
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[super pushViewController:viewController animated:animated];
[self setNavBarVisible];
}
-(void)setNavBarHidden {
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationBar setShadowImage:[UIImage new]];
[self.navigationBar setTranslucent:YES];
[self.navigationBar setBackgroundColor:[UIColor clearColor]];
}
-(void)setNavBarVisible {
[self.navigationBar setBackgroundColor:[UIColor grayColor]];
[self.navigationBar setBarTintColor:[UIColor grayColor]];
[self.navigationBar setTintColor:[UIColor whiteColor]];
[self.navigationBar setTranslucent:NO];
[self.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName :[UIColor whiteColor]}];
[self.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"Roboto-Reqular" size:18], NSFontAttributeName,nil]];
[self.navigationBar.topItem setBackBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]];
}
@end
O utilizando el método swizzling:
#import <objc/runtime.h>
#import "UINavigationController+FadeOutNavigationBar.h"
@implementation UINavigationController (FadeOutNavigationBar)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
//Swizzling view will appear
SEL originalSelectorVWA = @selector(viewWillAppear:);
SEL swizzledSelectorVWA = @selector(swizzled_viewWillAppear:);
Method originalMethodVWA = class_getInstanceMethod(class, originalSelectorVWA);
Method swizzledMethodVWA = class_getInstanceMethod(class, swizzledSelectorVWA);
BOOL didAddMethodVWA =
class_addMethod(class,
originalSelectorVWA,
method_getImplementation(swizzledMethodVWA),
method_getTypeEncoding(swizzledMethodVWA));
if (didAddMethodVWA) {
class_replaceMethod(class,
swizzledSelectorVWA,
method_getImplementation(originalMethodVWA),
method_getTypeEncoding(originalMethodVWA));
} else {
method_exchangeImplementations(originalMethodVWA, swizzledMethodVWA);
}
//Swizzling popViewControllerAnimated
SEL originalSelectorPVCA = @selector(popViewControllerAnimated:);
SEL swizzledSelectorPVCA = @selector(swizzled_popViewControllerAnimated:);
Method originalMethodPVCA = class_getInstanceMethod(class, originalSelectorPVCA);
Method swizzledMethodPVCA = class_getInstanceMethod(class, swizzledSelectorPVCA);
BOOL didAddMethodPVCA =
class_addMethod(class,
originalSelectorPVCA,
method_getImplementation(swizzledMethodPVCA),
method_getTypeEncoding(swizzledMethodPVCA));
if (didAddMethodPVCA) {
class_replaceMethod(class,
swizzledSelectorVWA,
method_getImplementation(originalMethodPVCA),
method_getTypeEncoding(originalMethodPVCA));
} else {
method_exchangeImplementations(originalMethodPVCA, swizzledMethodPVCA);
}
//Swizzling pushViewController
SEL originalSelectorPVC = @selector(pushViewController:animated:);
SEL swizzledSelectorPVC = @selector(swizzled_pushViewController:animated:);
Method originalMethodPVC = class_getInstanceMethod(class, originalSelectorPVC);
Method swizzledMethodPVC = class_getInstanceMethod(class, swizzledSelectorPVC);
BOOL didAddMethodPVC =
class_addMethod(class,
originalSelectorPVC,
method_getImplementation(swizzledMethodPVC),
method_getTypeEncoding(swizzledMethodPVC));
if (didAddMethodPVC) {
class_replaceMethod(class,
swizzledSelectorPVC,
method_getImplementation(originalMethodPVC),
method_getTypeEncoding(originalMethodPVC));
} else {
method_exchangeImplementations(originalMethodPVC, swizzledMethodPVC);
}
});
}
#pragma mark - Method Swizzling
- (void)swizzled_viewWillAppear:(BOOL)animated {
[self swizzled_viewWillAppear:animated];
NSLog(@"current: %@",[self.topViewController class]);
if ([NSStringFromClass([self.topViewController class]) isEqualToString:@"LoginVC"]) {
[self setNavBarHidden];
}
}
-(void)setNavBarHidden {
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationBar setShadowImage:[UIImage new]];
[self.navigationBar setTranslucent:YES];
[self.navigationBar setBackgroundColor:[UIColor clearColor]];
}
-(void)setNavBarVisible {
[self.navigationBar setBackgroundColor:[UIColor grayColor]];
[self.navigationBar setBarTintColor:[UIColor grayColor]];
[self.navigationBar setTintColor:[UIColor whiteColor]];
[self.navigationBar setTranslucent:NO];
[self.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName :[UIColor whiteColor]}];
[self.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"Roboto-Reqular" size:18], NSFontAttributeName,nil]];
[self.navigationBar.topItem setBackBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]];
}
-(UIViewController*)swizzled_popViewControllerAnimated:(BOOL)animated {
UIViewController *popedVC = [self swizzled_popViewControllerAnimated:animated];
if ([NSStringFromClass([popedVC class]) isEqualToString:@"SignUpVC"] && [NSStringFromClass([[self topViewController] class]) isEqualToString:@"LoginVC"]) {
[self setNavBarHidden];
}
return popedVC;
}
-(void)swizzled_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[self swizzled_pushViewController:viewController animated:animated];
[self setNavBarVisible];
}
@end
Trate de usar willMoveToParentViewController
, da el mismo efecto que anular los métodos UINavigationController
pero sin la molestia.
Tuve que arreglar esto con:
self.navigationController.navigationBarHidden = YES;
self.navigationController.navigationBarHidden = NO;
De esta manera, no tiene que anular el controlador popview o el controlador pushview. Básicamente está activando la barra de navegación para volver a dibujar.
Todavía es molesto cómo pueden simplemente lanzar una nueva versión del sistema operativo que rompe algo tan importante.