ios 11 UITabBar UITabBarItem positioning issue
Solution 1:
I am maintaining a large iPad app written mostly in Objective-C that has survived several iOS releases. I ran into the situation where I needed the pre-iOS 11 tab bar appearance (with the icons above the titles instead of next to them) for a couple tab bars. My solution was to create a subclass of UITabBar that overrides the traitCollection
method so that it always returns a horizontally-compact trait collection. This causes iOS 11 to display the titles below the icons for all of the tab bar buttons.
In order to use this, set the custom class of the tab bars in the storyboard to this new subclass and change any outlets in the code that point to the tab bars to be of this new type (don't forget to import the header file below).
The .h file is pretty much empty in this case:
//
// MyTabBar.h
//
#import <UIKit/UIKit.h>
@interface MyTabBar : UITabBar
@end
Here is the .m file with the implementation of the traitCollection
method:
//
// MyTabBar.m
//
#import "MyTabBar.h"
@implementation MyTabBar
// In iOS 11, UITabBarItem's have the title to the right of the icon in horizontally regular environments
// (i.e. the iPad). In order to keep the title below the icon, it was necessary to subclass UITabBar and override
// traitCollection to make it horizontally compact.
- (UITraitCollection *)traitCollection {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
}
@end
Solution 2:
Based on John C's answer, here is the Swift 3 version that can be used programmatically without need for Storyboard or subclassing:
extension UITabBar {
// Workaround for iOS 11's new UITabBar behavior where on iPad, the UITabBar inside
// the Master view controller shows the UITabBarItem icon next to the text
override open var traitCollection: UITraitCollection {
if UIDevice.current.userInterfaceIdiom == .pad {
return UITraitCollection(horizontalSizeClass: .compact)
}
return super.traitCollection
}
}
Solution 3:
To avoid messing up any other traits is it not better to combine with the superclasses:
- (UITraitCollection *)traitCollection
{
UITraitCollection *curr = [super traitCollection];
UITraitCollection *compact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
return [UITraitCollection traitCollectionWithTraitsFromCollections:@[curr, compact]];
}
Solution 4:
Swift 4 version with a subclass that avoids extension/category naming collision:
class TabBar: UITabBar {
override var traitCollection: UITraitCollection {
guard UIDevice.current.userInterfaceIdiom == .pad else {
return super.traitCollection
}
return UITraitCollection(horizontalSizeClass: .compact)
}
}
If you use Interface Builder and storyboards, you can select the tab bar view in your UITabBarController
scene and change its class to TabBar
in the Identity Inspector: