Подтвердить что ты не робот

Раскраска навигационной панели в 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};
}

Это ошибка? Есть ли что-то, что я могу сделать, чтобы исправить это? Это очень расстраивает.

4b9b3361

Ответ 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