UITabBar items jumping on back navigation on iOS 12.1

后端 未结 12 899
北恋
北恋 2020-11-29 18:28

I have an iOS app with UITabBarController on a master screen, navigating to a detail screen hiding the UITabBarController with setting hidesB

相关标签:
12条回答
  • 2020-11-29 18:59
    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)
        }
    }
    
    0 讨论(0)
  • 2020-11-29 19:01

    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

    0 讨论(0)
  • 2020-11-29 19:02

    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 UINavigationControllers.

    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

    0 讨论(0)
  • 2020-11-29 19:04

    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.

    0 讨论(0)
  • 2020-11-29 19:05

    In your UITabBarController, set isTranslucent = false

    0 讨论(0)
  • 2020-11-29 19:07

    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
    
    0 讨论(0)
提交回复
热议问题