ViewWillAppear 中的导航栏着色在 iOS 10 中发生得太晚了
Navigationbar coloring in ViewWillAppear happens too late in iOS 10
我遇到了一个奇怪的错误,它只发生在 iOS 10.
我有一个包含多个屏幕的应用程序,每个屏幕都为 viewWillAppear
中的 navigationBar
着色。所以当你转到下一个屏幕时,它会正确着色。
但是,在 iOS 10 上进行测试时,我在返回到上一个屏幕时突然看到以下行为:
当上一个屏幕出现时,navigationBar
仍然具有前一个屏幕的颜色,然后闪烁到正确的颜色。
它几乎看起来像 viewWillAppear
以某种方式表现得像 viewDidAppear
.
相关代码:
ViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[ViewControllerPainter paint:self withBackground:[UIColor whiteColor] andForeground:[UIColor blackColor] andIsLight:true];
}
画家:
+ (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};
}
这是一个错误吗?我可以做些什么来解决这个问题吗?很郁闷。
这是根据 iOS 10 SDK Release Notes 更改的内容:
In iOS 10, UIKit has updated and unified background management for UINavigationBar, UITabBar, and UIToolbar. In particular, changes to background properties of these views (such as background or shadow images, or setting the bar style) may kick off a layout pass for the bar to resolve the new background appearance.
In particular, this means that attempts to change the background appearance of these bars inside of -[UIView layoutSubviews], -[UIView updateConstraints], -[UIViewController willLayoutSubviews], -[UIViewController didLayoutSubviews], - [UIViewController updateViewConstraints], or any other method that is called in response to layout may result in a layout loop.
所以问题似乎是 viewWillAppear
触发了提到的布局循环,因为它是由于布局更改而调用的:
我的快速修复是覆盖 popViewControllerAnimated
和 pushViewController
并更新 UINavigationController
子类的 navigationBar
背景。它是这样的:
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
不是由于布局更改而被 OS 调用,而是被触摸事件调用。因此,如果您想找到另一个地方来更新您的 navigationBar
背景,请记住这一点。
我正在发布 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
或使用方法调配:
#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
尝试使用 willMoveToParentViewController
,提供与覆盖 UINavigationController
方法相同的效果,但没有麻烦。
我不得不用以下方法解决这个问题:
self.navigationController.navigationBarHidden = YES;
self.navigationController.navigationBarHidden = NO;
这样您就不必覆盖 popviewcontroller 或 pushviewcontroller。它基本上是触发导航栏重绘。
他们怎么能推出新版本的 OS 却破坏了这么重要的东西,这仍然很烦人。
我遇到了一个奇怪的错误,它只发生在 iOS 10.
我有一个包含多个屏幕的应用程序,每个屏幕都为 viewWillAppear
中的 navigationBar
着色。所以当你转到下一个屏幕时,它会正确着色。
但是,在 iOS 10 上进行测试时,我在返回到上一个屏幕时突然看到以下行为:
当上一个屏幕出现时,navigationBar
仍然具有前一个屏幕的颜色,然后闪烁到正确的颜色。
它几乎看起来像 viewWillAppear
以某种方式表现得像 viewDidAppear
.
相关代码:
ViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[ViewControllerPainter paint:self withBackground:[UIColor whiteColor] andForeground:[UIColor blackColor] andIsLight:true];
}
画家:
+ (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};
}
这是一个错误吗?我可以做些什么来解决这个问题吗?很郁闷。
这是根据 iOS 10 SDK Release Notes 更改的内容:
In iOS 10, UIKit has updated and unified background management for UINavigationBar, UITabBar, and UIToolbar. In particular, changes to background properties of these views (such as background or shadow images, or setting the bar style) may kick off a layout pass for the bar to resolve the new background appearance.
In particular, this means that attempts to change the background appearance of these bars inside of -[UIView layoutSubviews], -[UIView updateConstraints], -[UIViewController willLayoutSubviews], -[UIViewController didLayoutSubviews], - [UIViewController updateViewConstraints], or any other method that is called in response to layout may result in a layout loop.
所以问题似乎是 viewWillAppear
触发了提到的布局循环,因为它是由于布局更改而调用的:
我的快速修复是覆盖 popViewControllerAnimated
和 pushViewController
并更新 UINavigationController
子类的 navigationBar
背景。它是这样的:
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
不是由于布局更改而被 OS 调用,而是被触摸事件调用。因此,如果您想找到另一个地方来更新您的 navigationBar
背景,请记住这一点。
我正在发布 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
或使用方法调配:
#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
尝试使用 willMoveToParentViewController
,提供与覆盖 UINavigationController
方法相同的效果,但没有麻烦。
我不得不用以下方法解决这个问题:
self.navigationController.navigationBarHidden = YES;
self.navigationController.navigationBarHidden = NO;
这样您就不必覆盖 popviewcontroller 或 pushviewcontroller。它基本上是触发导航栏重绘。
他们怎么能推出新版本的 OS 却破坏了这么重要的东西,这仍然很烦人。