I have an iOS app with UITabBarController
on a master screen, navigating to a detail screen hiding the UITabBarController
with setting hidesB
import UIKit
extension UITabBar{
open override func layoutSubviews() {
super.layoutSubviews()
if let UITabBarButtonClass = NSClassFromString("UITabBarButton") as? NSObject.Type{
let subItems = self.subviews.filter({return $0.isKind(of: UITabBarButtonClass)})
if subItems.count > 0{
let tmpWidth = UIScreen.main.bounds.width / CGFloat(subItems.count)
for (index,item) in subItems.enumerated(){
item.frame = CGRect(x: CGFloat(index) * tmpWidth, y: 0, width: tmpWidth, height: item.bounds.height)
}
}
}
}
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let view:UITabBar = super.hitTest(point, with: event) as? UITabBar{
for item in view.subviews{
if point.x >= item.frame.origin.x && point.x <= item.frame.origin.x + item.frame.size.width{
return item
}
}
}
return super.hitTest(point, with: event)
}
}
there are two ways to fix this issue, Firstly, In your UITabBarController, set isTranslucent = false like:
[[UITabBar appearance] setTranslucent:NO];
sencondly, if the first solution does not fix your issur, try this way:
here is the Objective-C code
// .h
@interface CYLTabBar : UITabBar
@end
// .m
#import "CYLTabBar.h"
CG_INLINE BOOL
OverrideImplementation(Class targetClass, SEL targetSelector, id (^implementationBlock)(Class originClass, SEL originCMD, IMP originIMP)) {
Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
if (!originMethod) {
return NO;
}
IMP originIMP = method_getImplementation(originMethod);
method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originIMP)));
return YES;
}
@implementation CYLTabBar
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(iOS 12.1, *)) {
OverrideImplementation(NSClassFromString(@"UITabBarButton"), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
return ^(UIView *selfObject, CGRect firstArgv) {
if ([selfObject isKindOfClass:originClass]) {
if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) {
return;
}
}
// call super
void (*originSelectorIMP)(id, SEL, CGRect);
originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
originSelectorIMP(selfObject, originCMD, firstArgv);
};
});
}
});
}
@end
More information:https://github.com/ChenYilong/CYLTabBarController/commit/2c741c8bffd47763ad2fca198202946a2a63c4fc
I was facing the exact same issue, where the app was architectured with one navigation controller per tab. The easiest non-hacky way that I found to fix this, was to place the UITabBarController
inside a UINavigationController
, and remove the individual UINavigationController
s.
Before:
-> UINavigationController -> UIViewController
-> UINavigationController -> UIViewController
UITabBarController -> UINavigationController -> UIViewController
-> UINavigationController -> UIViewController
-> UINavigationController -> UIViewController
After:
-> UIViewController
-> UIViewController
UINavigationController -> UITabBarController -> UIViewController
-> UIViewController
-> UIViewController
By using the outer UINavigationController
, you don't need to hide the UITabBar
when pushing a view controller onto the navigation stack.
Caveat:
The only issue I found so far, is that setting the title or right/left bar button items on each UIViewController
does not have the same effect. To overcome this issue, I applied the changes via the UITabBarControllerDelegate
when the visible UIViewController
has changed.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let topItem = self.navigationController?.navigationBar.topItem else { return }
precondition(self.navigationController == viewController.navigationController, "Navigation controllers do not match. The following changes might result in unexpected behaviour.")
topItem.title = viewController.title
topItem.titleView = viewController.navigationItem.titleView
topItem.leftBarButtonItem = viewController.navigationItem.leftBarButtonItem
topItem.rightBarButtonItem = viewController.navigationItem.rightBarButtonItem
}
Note that I have added a preconditionFailure
to catch any case when the navigation architecture has been modified
In my case (iOS 12.1.4), I found that this weird glitchy behaviour was triggered by modals being presented with the .modalPresentationStyle = .fullScreen
After updating their presentationStyle to .overFullScreen
, the glitch went away.
In your UITabBarController
, set isTranslucent = false
Thanks for the idea of @ElonChan,
I just changed the c inline function to OC static method, since I won't use this overrideImplementation
too much. And also, this snippet was adjusted to iPhoneX now.
static CGFloat const kIPhoneXTabbarButtonErrorHeight = 33;
static CGFloat const kIPhoneXTabbarButtonHeight = 48;
@implementation FixedTabBar
typedef void(^NewTabBarButtonFrameSetter)(UIView *, CGRect);
typedef NewTabBarButtonFrameSetter (^ImpBlock)(Class originClass, SEL originCMD, IMP originIMP);
+ (BOOL)overrideImplementationWithTargetClass:(Class)targetClass targetSelector:(SEL)targetSelector implementBlock:(ImpBlock)implementationBlock {
Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
if (!originMethod) {
return NO;
}
IMP originIMP = method_getImplementation(originMethod);
method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originIMP)));
return YES;
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(iOS 12.1, *)) {
[self overrideImplementationWithTargetClass:NSClassFromString(@"UITabBarButton")
targetSelector:@selector(setFrame:)
implementBlock:^NewTabBarButtonFrameSetter(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
return ^(UIView *selfObject, CGRect firstArgv) {
if ([selfObject isKindOfClass:originClass]) {
if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) {
return;
}
if (firstArgv.size.height == kIPhoneXTabbarButtonErrorHeight) {
firstArgv.size.height = kIPhoneXTabbarButtonHeight;
}
}
void (*originSelectorIMP)(id, SEL, CGRect);
originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
originSelectorIMP(selfObject, originCMD, firstArgv);
};
}];
}
});
}
@end