Раскраска навигационной панели в ViewWillAppear происходит слишком поздно в iOS 10
Я столкнулся с странной ошибкой, которая происходит только на iOS 10.
У меня есть приложение с несколькими экранами, и каждый экран раскрашивает navigationBar
в viewWillAppear
. Поэтому, когда вы переходите к следующему экрану, он будет правильно окрашен.
Однако при тестировании на iOS 10 я неожиданно вижу следующее поведение при возвращении к предыдущему экрану:
Когда появится предыдущий экран, navigationBar
по-прежнему имеет цвет предыдущего экрана, а затем мигает до нужного цвета.
Он почти выглядит как viewWillAppear
как-то ведет себя как viewDidAppear
.
Соответствующий код:
ViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[ViewControllerPainter paint:self withBackground:[UIColor whiteColor] andForeground:[UIColor blackColor] andIsLight:true];
}
Painter:
+ (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};
}
Это ошибка? Есть ли что-то, что я могу сделать, чтобы исправить это? Это очень расстраивает.
Ответы
Ответ 1
Вот что изменилось в соответствии с примечаниями к выпуску SDK для iOS 10:
В iOS 10 UIKit имеет обновленное и унифицированное управление фоном для UINavigationBar, UITabBar и UIToolbar. В частности, изменения фоновых свойств этих представлений (например, фоновые или теневые изображения или установка стиля бара) могут инициировать прохождение макета для панели, чтобы разрешить появление нового фона.
В частности, это означает, что попытки изменить внешний вид этих столбцов внутри - [UIView layoutSubviews], - [UIView updateConstraints], - [UIViewController willLayoutSubviews], - [UIViewController didLayoutSubviews], - [UIViewController updateViewConstraints] или любые другие метод, который вызывается в ответ на макет, может привести к циклу компоновки.
Таким образом, проблема заключается в том, что viewWillAppear
запускает указанный цикл компоновки, поскольку он вызван в результате изменения макета:
![viewWillAppear трассировка стека]()
Быстрое исправление для меня было переопределение popViewControllerAnimated
и pushViewController
и обновление фона navigationBar
в моем подклассе UINavigationController
. Вот как это выглядит:
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)
}
Я предполагаю, что он работает, потому что popViewControllerAnimated
и pushViewController
не вызываются ОС в результате изменения макета, а прикосновением. Поэтому имейте это в виду, если вы хотите найти другое место для обновления фона navigationBar
.
Ответ 2
Мне пришлось исправить это с помощью
self.navigationController.navigationBarHidden = YES;
self.navigationController.navigationBarHidden = NO;
Таким образом, вам не нужно переопределять контроллер popviewcontroller или pushviewcontroller. Это в основном заставляет навигационную панель перерисовывать.
Это все еще раздражает то, как они могут просто выпустить новую версию ОС, которая нарушает что-то значимое.
Ответ 3
Попробуйте использовать willMoveToParentViewController
, что дает тот же эффект, что и переопределение методов UINavigationController
, но без хлопот.
Ответ 4
Я отправляю решение для Objective-C (подкласс 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
ИЛИ используя метод 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