UITabBarController + UINavigationController 等选项卡黑屏问题
UITabBarController + UINavigationController and more tab black screen issue
我目前正在更新我的一个应用程序,我遇到了一个与 UITabBarController 有关的非常奇怪的问题。
在我的故事板中,我有大约 8 个视图控制器,在我的 UITabBarController 子类中,我添加了另外 4 个以编程方式加载的视图控制器。大多数这些视图都需要 UINavigationController 在旋转时保持一致性,因为一些视图从 "More" 选项卡进入主栏,为了做到这一点,我将它们嵌入到 UINavigationController 中。
如果您选择纵向视图 6 并旋转 UINavigationController,当视图在标签栏中获得自己的按钮时,UINavigationController 会变黑,但是当它 returns 到 "more" 时,视图会返回。在我对这些的调查中,似乎 UINavigationController 丢失了 UIViewController 作为它的根视图控制器。
在未进入 "More" 选项卡的视图上按预期工作:imgur.com/gVB8wTF
如果视图来自 "More" 选项卡,则黑屏:http://imgur.com/WaoNoL1
我做了一个有这个问题的快速示例项目:https://github.com/joshluongo/UITabBarController-Issues
关于如何解决这个问题有什么想法吗?
我找到了似乎可以解决此问题的解决方法。
通过覆盖 UITabBarController
子类中的 UITraitCollection
,您可以强制 horizontalSizeClass
始终为 UIUserInterfaceSizeClassCompact
。这将使 UITabBar
无论方向如何都只有 5 个项目。
这里有一些示例 Objective-C 代码:
- (UITraitCollection *)traitCollection {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
// Workaround to fix the iPhone 6 Plus roatation issue.
UITraitCollection *curr = [super traitCollection];
UITraitCollection *compact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
return [UITraitCollection traitCollectionWithTraitsFromCollections:@[curr, compact]];
}
return [super traitCollection];
}
然后,如果您需要访问真实特征,则将 UIViewController
中的 -traitCollection
覆盖为 return 来自 [UIScreen mainScreen]
.
的特征
这里有一些示例 Objective-C 代码可以做到这一点:
- (UITraitCollection *)traitCollection {
return [UIScreen mainScreen].traitCollection;
}
这不是一个理想的解决方案,但在 Apple 决定修复此错误之前,这将完成工作。
我希望这对某人有所帮助。
rdar://21297168
我运行遇到了同样的问题。
我想出了一个非常有效的解决方法。我已将其推到 Github 此处:https://github.com/jfahrenkrug/UITabBarControllerMoreBugWorkaround
欢迎任何改进。
这个错误的发生是因为你的 UINavigationController 的堆栈被从它移除并放入私有的 UIMoreNavigationController 中。但是在旋转回常规宽度后,该堆栈未正确放回其原始 UINavigationViewController。
解决方案是继承 UITabBarController 并将其 willTransitionToTraitCollection:withTransitionCoordinator:
替换为这个:
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
//#define MORE_TAB_DEBUG 1
#ifdef MORE_TAB_DEBUG
#define MoreTabDLog(fmt, ...) NSLog((@"[More Tab Debug] " fmt), ##__VA_ARGS__);
#else
#define MoreTabDLog(...)
#endif
MoreTabDLog(@"-- before willTransitionToTraitCollection");
/*
There is a bug when going in and out of the compact size class when a tab bar
controller has more than 5 tabs. See http://www.openradar.me/25393521
It comes down to this: When you have more than 5 tabs and a view controller on a tab
beyond the 4th tab is a UINavigationController, you have a problem.
When you are on this tab in compact and push one or more VCs onto the stack and then
change back to regular width, only the top most view controller will be added back onto the
stack.
This happens because the stack of your UINavigationController is taken out of that NavVC and put
into the private UIMoreNavigationController. But upon rotating back to regular, that stack is not
correctly put back into your own NavVC.
We have 3 cases we have to handle:
1) We are on the "More" tab in compact and are looking at the UIMoreListController and then change to
regular size.
2) While in compact width, we are on a tab greater than the 4th and are changing to regular width.
3) While in regular width, we are on a tab greater than the 4th and are changing to compact width.
*/
if ((self.traitCollection.horizontalSizeClass != newCollection.horizontalSizeClass) ||
(self.traitCollection.verticalSizeClass != newCollection.verticalSizeClass))
{
/*
Case 1: We are on the "More" tab in compact and are looking at the UIMoreListController and then change to regular size.
*/
if ([self.selectedViewController isKindOfClass:[UINavigationController class]] && [NSStringFromClass([self.selectedViewController class]) hasPrefix:@"UIMore"]) {
// We are on the root of the MoreViewController in compact, going into regular.
// That means we have to pop all the viewControllers in the MoreViewController to root
#ifdef MORE_TAB_DEBUG
UINavigationController *moreNavigationController = (UINavigationController *)self.selectedViewController;
UIViewController *moreRootViewController = [moreNavigationController topViewController];
MoreTabDLog(@"-- going OUT of compact while on UIMoreList");
MoreTabDLog(@"moreRootViewController: %@", moreRootViewController);
#endif
for (NSInteger overflowVCIndex = 4; overflowVCIndex < [self.viewControllers count]; overflowVCIndex++) {
if ([self.viewControllers[overflowVCIndex] isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)self.viewControllers[overflowVCIndex];
MoreTabDLog(@"popping %@ to root", navigationController);
[navigationController popToRootViewControllerAnimated:NO];
}
}
} else {
BOOL isPotentiallyInOverflow = [self.viewControllers indexOfObject:self.selectedViewController] >= 4;
MoreTabDLog(@"isPotentiallyInOverflow: %i", isPotentiallyInOverflow);
if (isPotentiallyInOverflow && [self.selectedViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *selectedNavController = (UINavigationController *)self.selectedViewController;
NSArray<UIViewController *> *selectedNavControllerStack = [selectedNavController viewControllers];
MoreTabDLog(@"Selected Nav: %@, selectedNavStack: %@", selectedNavController, selectedNavControllerStack);
UIViewController *lastChildVCOfTabBar = [[self childViewControllers] lastObject];
if ([lastChildVCOfTabBar isKindOfClass:[UINavigationController class]] && [NSStringFromClass([lastChildVCOfTabBar class]) hasPrefix:@"UIMore"]) {
/*
Case 2: While in compact width, we are on a tab greater than the 4th and are changing to regular width.
We are going OUT of compact
*/
UINavigationController *moreNavigationController = (UINavigationController *)lastChildVCOfTabBar;
NSArray *moreNavigationControllerStack = [moreNavigationController viewControllers];
MoreTabDLog(@"--- going OUT of compact");
MoreTabDLog(@"moreNav: %@, moreNavStack: %@, targetNavStack: %@", moreNavigationController, moreNavigationControllerStack, selectedNavControllerStack);
if ([moreNavigationControllerStack count] > 1) {
NSArray *fixedTargetStack = [moreNavigationControllerStack subarrayWithRange:NSMakeRange(1, moreNavigationControllerStack.count - 1)];
MoreTabDLog(@"fixedTargetStack: %@", fixedTargetStack);
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *correctVCList = [NSArray arrayWithArray:self.viewControllers];
[selectedNavController willMoveToParentViewController:self];
[selectedNavController setViewControllers:fixedTargetStack animated:NO];
// We need to do this because without it, the selectedNavController doesn't
// have a parentViewController anymore.
[self addChildViewController:selectedNavController];
// We need to do this because otherwise the previous call will cause the given
// Tab to show up twice in the UIMoreListController.
[self setViewControllers:correctVCList];
});
} else {
MoreTabDLog(@"popping to root");
dispatch_async(dispatch_get_main_queue(), ^{
[selectedNavController popToRootViewControllerAnimated:NO];
});
}
} else {
/*
Case 3: While in regular width, we are on a tab greater than the 4th and are changing to compact width.
We are going INTO compact
*/
MoreTabDLog(@"-- going INTO compact");
if ([selectedNavControllerStack count] > 0) {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// no op
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
UIViewController *parentViewControllerOfTopVC = [[selectedNavControllerStack lastObject] parentViewController];
MoreTabDLog(@"parentViewControllerOfTopVC: %@", parentViewControllerOfTopVC);
if ([parentViewControllerOfTopVC isKindOfClass:[UINavigationController class]] && [NSStringFromClass([parentViewControllerOfTopVC class]) hasPrefix:@"UIMore"]) {
UINavigationController *moreNavigationController = (UINavigationController *)parentViewControllerOfTopVC;
NSArray *moreNavigationControllerStack = [moreNavigationController viewControllers];
BOOL isOriginalRootVCInMoreStack = [moreNavigationControllerStack containsObject:[selectedNavControllerStack firstObject]];
MoreTabDLog(@"moreNav: %@, moreNavStack: %@, isOriginalRootVCInMoreStack: %i", moreNavigationController, moreNavigationControllerStack, isOriginalRootVCInMoreStack);
if (!isOriginalRootVCInMoreStack) {
NSArray *fixedMoreStack = [@[moreNavigationControllerStack[0]] arrayByAddingObjectsFromArray:selectedNavControllerStack];
MoreTabDLog(@"fixedMoreStack: %@", fixedMoreStack);
[selectedNavController setViewControllers:selectedNavControllerStack animated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[moreNavigationController setViewControllers:fixedMoreStack animated:NO];
});
}
}
}];
}
}
}
}
}
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
MoreTabDLog(@"-- after willTransitionToTraitCollection");
}
尽情享受吧!
约翰内斯
我目前正在更新我的一个应用程序,我遇到了一个与 UITabBarController 有关的非常奇怪的问题。
在我的故事板中,我有大约 8 个视图控制器,在我的 UITabBarController 子类中,我添加了另外 4 个以编程方式加载的视图控制器。大多数这些视图都需要 UINavigationController 在旋转时保持一致性,因为一些视图从 "More" 选项卡进入主栏,为了做到这一点,我将它们嵌入到 UINavigationController 中。
如果您选择纵向视图 6 并旋转 UINavigationController,当视图在标签栏中获得自己的按钮时,UINavigationController 会变黑,但是当它 returns 到 "more" 时,视图会返回。在我对这些的调查中,似乎 UINavigationController 丢失了 UIViewController 作为它的根视图控制器。
在未进入 "More" 选项卡的视图上按预期工作:imgur.com/gVB8wTF
如果视图来自 "More" 选项卡,则黑屏:http://imgur.com/WaoNoL1
我做了一个有这个问题的快速示例项目:https://github.com/joshluongo/UITabBarController-Issues
关于如何解决这个问题有什么想法吗?
我找到了似乎可以解决此问题的解决方法。
通过覆盖 UITabBarController
子类中的 UITraitCollection
,您可以强制 horizontalSizeClass
始终为 UIUserInterfaceSizeClassCompact
。这将使 UITabBar
无论方向如何都只有 5 个项目。
这里有一些示例 Objective-C 代码:
- (UITraitCollection *)traitCollection {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
// Workaround to fix the iPhone 6 Plus roatation issue.
UITraitCollection *curr = [super traitCollection];
UITraitCollection *compact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
return [UITraitCollection traitCollectionWithTraitsFromCollections:@[curr, compact]];
}
return [super traitCollection];
}
然后,如果您需要访问真实特征,则将 UIViewController
中的 -traitCollection
覆盖为 return 来自 [UIScreen mainScreen]
.
这里有一些示例 Objective-C 代码可以做到这一点:
- (UITraitCollection *)traitCollection {
return [UIScreen mainScreen].traitCollection;
}
这不是一个理想的解决方案,但在 Apple 决定修复此错误之前,这将完成工作。
我希望这对某人有所帮助。
rdar://21297168
我运行遇到了同样的问题。
我想出了一个非常有效的解决方法。我已将其推到 Github 此处:https://github.com/jfahrenkrug/UITabBarControllerMoreBugWorkaround
欢迎任何改进。
这个错误的发生是因为你的 UINavigationController 的堆栈被从它移除并放入私有的 UIMoreNavigationController 中。但是在旋转回常规宽度后,该堆栈未正确放回其原始 UINavigationViewController。
解决方案是继承 UITabBarController 并将其 willTransitionToTraitCollection:withTransitionCoordinator:
替换为这个:
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
//#define MORE_TAB_DEBUG 1
#ifdef MORE_TAB_DEBUG
#define MoreTabDLog(fmt, ...) NSLog((@"[More Tab Debug] " fmt), ##__VA_ARGS__);
#else
#define MoreTabDLog(...)
#endif
MoreTabDLog(@"-- before willTransitionToTraitCollection");
/*
There is a bug when going in and out of the compact size class when a tab bar
controller has more than 5 tabs. See http://www.openradar.me/25393521
It comes down to this: When you have more than 5 tabs and a view controller on a tab
beyond the 4th tab is a UINavigationController, you have a problem.
When you are on this tab in compact and push one or more VCs onto the stack and then
change back to regular width, only the top most view controller will be added back onto the
stack.
This happens because the stack of your UINavigationController is taken out of that NavVC and put
into the private UIMoreNavigationController. But upon rotating back to regular, that stack is not
correctly put back into your own NavVC.
We have 3 cases we have to handle:
1) We are on the "More" tab in compact and are looking at the UIMoreListController and then change to
regular size.
2) While in compact width, we are on a tab greater than the 4th and are changing to regular width.
3) While in regular width, we are on a tab greater than the 4th and are changing to compact width.
*/
if ((self.traitCollection.horizontalSizeClass != newCollection.horizontalSizeClass) ||
(self.traitCollection.verticalSizeClass != newCollection.verticalSizeClass))
{
/*
Case 1: We are on the "More" tab in compact and are looking at the UIMoreListController and then change to regular size.
*/
if ([self.selectedViewController isKindOfClass:[UINavigationController class]] && [NSStringFromClass([self.selectedViewController class]) hasPrefix:@"UIMore"]) {
// We are on the root of the MoreViewController in compact, going into regular.
// That means we have to pop all the viewControllers in the MoreViewController to root
#ifdef MORE_TAB_DEBUG
UINavigationController *moreNavigationController = (UINavigationController *)self.selectedViewController;
UIViewController *moreRootViewController = [moreNavigationController topViewController];
MoreTabDLog(@"-- going OUT of compact while on UIMoreList");
MoreTabDLog(@"moreRootViewController: %@", moreRootViewController);
#endif
for (NSInteger overflowVCIndex = 4; overflowVCIndex < [self.viewControllers count]; overflowVCIndex++) {
if ([self.viewControllers[overflowVCIndex] isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)self.viewControllers[overflowVCIndex];
MoreTabDLog(@"popping %@ to root", navigationController);
[navigationController popToRootViewControllerAnimated:NO];
}
}
} else {
BOOL isPotentiallyInOverflow = [self.viewControllers indexOfObject:self.selectedViewController] >= 4;
MoreTabDLog(@"isPotentiallyInOverflow: %i", isPotentiallyInOverflow);
if (isPotentiallyInOverflow && [self.selectedViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *selectedNavController = (UINavigationController *)self.selectedViewController;
NSArray<UIViewController *> *selectedNavControllerStack = [selectedNavController viewControllers];
MoreTabDLog(@"Selected Nav: %@, selectedNavStack: %@", selectedNavController, selectedNavControllerStack);
UIViewController *lastChildVCOfTabBar = [[self childViewControllers] lastObject];
if ([lastChildVCOfTabBar isKindOfClass:[UINavigationController class]] && [NSStringFromClass([lastChildVCOfTabBar class]) hasPrefix:@"UIMore"]) {
/*
Case 2: While in compact width, we are on a tab greater than the 4th and are changing to regular width.
We are going OUT of compact
*/
UINavigationController *moreNavigationController = (UINavigationController *)lastChildVCOfTabBar;
NSArray *moreNavigationControllerStack = [moreNavigationController viewControllers];
MoreTabDLog(@"--- going OUT of compact");
MoreTabDLog(@"moreNav: %@, moreNavStack: %@, targetNavStack: %@", moreNavigationController, moreNavigationControllerStack, selectedNavControllerStack);
if ([moreNavigationControllerStack count] > 1) {
NSArray *fixedTargetStack = [moreNavigationControllerStack subarrayWithRange:NSMakeRange(1, moreNavigationControllerStack.count - 1)];
MoreTabDLog(@"fixedTargetStack: %@", fixedTargetStack);
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *correctVCList = [NSArray arrayWithArray:self.viewControllers];
[selectedNavController willMoveToParentViewController:self];
[selectedNavController setViewControllers:fixedTargetStack animated:NO];
// We need to do this because without it, the selectedNavController doesn't
// have a parentViewController anymore.
[self addChildViewController:selectedNavController];
// We need to do this because otherwise the previous call will cause the given
// Tab to show up twice in the UIMoreListController.
[self setViewControllers:correctVCList];
});
} else {
MoreTabDLog(@"popping to root");
dispatch_async(dispatch_get_main_queue(), ^{
[selectedNavController popToRootViewControllerAnimated:NO];
});
}
} else {
/*
Case 3: While in regular width, we are on a tab greater than the 4th and are changing to compact width.
We are going INTO compact
*/
MoreTabDLog(@"-- going INTO compact");
if ([selectedNavControllerStack count] > 0) {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// no op
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
UIViewController *parentViewControllerOfTopVC = [[selectedNavControllerStack lastObject] parentViewController];
MoreTabDLog(@"parentViewControllerOfTopVC: %@", parentViewControllerOfTopVC);
if ([parentViewControllerOfTopVC isKindOfClass:[UINavigationController class]] && [NSStringFromClass([parentViewControllerOfTopVC class]) hasPrefix:@"UIMore"]) {
UINavigationController *moreNavigationController = (UINavigationController *)parentViewControllerOfTopVC;
NSArray *moreNavigationControllerStack = [moreNavigationController viewControllers];
BOOL isOriginalRootVCInMoreStack = [moreNavigationControllerStack containsObject:[selectedNavControllerStack firstObject]];
MoreTabDLog(@"moreNav: %@, moreNavStack: %@, isOriginalRootVCInMoreStack: %i", moreNavigationController, moreNavigationControllerStack, isOriginalRootVCInMoreStack);
if (!isOriginalRootVCInMoreStack) {
NSArray *fixedMoreStack = [@[moreNavigationControllerStack[0]] arrayByAddingObjectsFromArray:selectedNavControllerStack];
MoreTabDLog(@"fixedMoreStack: %@", fixedMoreStack);
[selectedNavController setViewControllers:selectedNavControllerStack animated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[moreNavigationController setViewControllers:fixedMoreStack animated:NO];
});
}
}
}];
}
}
}
}
}
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
MoreTabDLog(@"-- after willTransitionToTraitCollection");
}
尽情享受吧!
约翰内斯