UITabBar在iPhoneX等Push时显示错乱问题

问题描述

看图说话吧,用慢动作,看得明显一些


UITabBar在iPhoneX等Push时显示错乱问题_第1张图片
跳一下

先Present,再Push,看到TabBar 跳了一下,真机上有时会不跳,就显示一半的情况。

解决方案

直接上解决方案:
替换 UITabBar 的 setFrame方法

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;
}

static CGFloat const kIPhoneXTabbarHeight = 83;

+ (BOOL)iPhoneX {
    if (@available(iOS 11.0, *)) {
        BOOL result = NO;
        UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
            UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
            if (orientation == UIInterfaceOrientationUnknown || orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
                result = window.safeAreaInsets.top > 0 && window.safeAreaInsets.bottom > 0;
            } else {
                result = window.safeAreaInsets.bottom > 0 && window.safeAreaInsets.left > 0 && window.safeAreaInsets.right > 0;
            }
        }
        return result;
    }
    return NO;
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        OverrideImplementation(NSClassFromString(@"UITabBar"), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
            return ^(UIView *selfObject, CGRect firstArgv) {
                if ([self iPhoneX]) {
                    if (firstArgv.size.height != kIPhoneXTabbarHeight) {
                        firstArgv.size.height = kIPhoneXTabbarHeight;
                    }
                    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];

                    CGFloat y =window.bounds.size.height -kIPhoneXTabbarHeight;
                    if (firstArgv.origin.y != y) {
                        firstArgv.origin.y = y;
                    }
                }

                // call super
                void (*originSelectorIMP)(id, SEL, CGRect);
                originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
                originSelectorIMP(selfObject, originCMD, firstArgv);
            };
        });
    });
}

(只想找答案的就看这里吧)

分析过程

起初并不知道这一问题重现的步骤,一直以为是偶现的,没注意,后来发现这个问题次数很多,就开始关注了。

重现步骤:

APP里面有UITabBar,每个Tab页里都有一个Nav,并且在这个页面上先Present一个页后,关闭Present的View,再Push到新的页面,就会出现这个现象。
还有几个前提条件:
1、iPhoneX 系列
2、TabBar的translucent属性为 YES,即半透明磨砂状的
3、新的页面的hidesBottomBarWhenPushed 是 YES的,即要TabBar要消失
4、Push时,一定是使用动画效果的(这是个废话)
这么多条件下才能出现,可见这个Bug应该不算大概率的Bug了。

首先,知道了重现的步骤,那就比较一下Present一个View后,TabBar具体发生了什么变化。
从两次的表现来看,我们发现
在异常情况下 Push时 TabBar是到进入页面后,才消失的。
而正常情况下,Push时,TabBar一直跟随第一个页面,感觉这两种情况下TabBar的父窗体都不同的样子。

那就从堆栈看一下,UIView有一个方法 - (void)willMoveToSuperview:(nullable UIView *)newSuperview 可以捕获到父窗体变更,我们用个Category实现一下UITabBar的这个方法,看一下变更时的堆栈
参考代码:

@implementation UITabBar (FixPush)
- (void)willMoveToSuperview:(nullable UIView *)newSuperview{
    NSLog(@"要换了 %@",newSuperview);
    NSLog(@"---%@",[NSThread callStackSymbols]);
}
@end

【正常情况下的 push】

2019-06-20 09:40:19.119639+0800 XXTabBar[10773:55905] 要换了 >
2019-06-20 09:40:19.122909+0800 XXTabBar[10773:55905] ---(
    0   XXTabBar                            0x0000000100481c63 -[UITabBar(FixPush) willMoveToSuperview:] + 83
    1   UIKitCore                           0x0000000104ab70c2 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 545
    2   UIKitCore                           0x0000000103f20ee1 -[UITabBarController _hideBarWithTransition:isExplicit:duration:] + 1058
    3   UIKitCore                           0x0000000103f564c5 -[UINavigationController _hideOrShowBottomBarIfNeededWithTransition:] + 975
    4   UIKitCore                           0x0000000103f55484 -[UINavigationController pushViewController:transition:forceImmediate:] + 1850
    5   UIKitCore                           0x0000000103f54bac -[UINavigationController pushViewController:animated:] + 681
    6   XXTabBar                            0x0000000100482700 -[FirstViewController gotoNext:] + 208
    7   UIKitCore                           0x00000001045f1204 -[UIApplication sendAction:to:from:forEvent:] + 83
    8   UIKitCore                           0x00
2019-06-20 09:40:19.638141+0800 XXTabBar[10773:55905] 要换了 >
2019-06-20 09:40:19.640726+0800 XXTabBar[10773:55905] ---(
    0   XXTabBar                            0x0000000100481c63 -[UITabBar(FixPush) willMoveToSuperview:] + 83
    1   UIKitCore                           0x0000000104ab70c2 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 545
    2   UIKitCore                           0x0000000103f34ce8 -[UILayoutContainerView addSubview:] + 75
    3   UIKitCore                           0x0000000103f216fa __65-[UITabBarController _hideBarWithTransition:isExplicit:duration:]_block_invoke.661 + 65
    4   UIKitCore                           0x000000010401ced8 -[_UIViewControllerTransitionCoordinator _applyBlocks:releaseBlocks:] + 294
    5   UIKitCore                           0x0000000104018cfd -[_UIViewControllerTransitionContext _runAlongsideCompletions] + 132
    6   UIKitCore                           0x00000001040189ec -[_UIViewControllerTransitionContext completeTransition:] + 118
    7   UIKitCore                           0x000000010402a686 __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke.118 + 877

【异常情况下的 push】

2019-06-20 09:40:08.056567+0800 XXTabBar[10773:55905] 要换了 >
2019-06-20 09:40:08.060075+0800 XXTabBar[10773:55905] ---(
    0   XXTabBar                            0x0000000100481c63 -[UITabBar(FixPush) willMoveToSuperview:] + 83
    1   UIKitCore                           0x0000000104ab70c2 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 545
    2   UIKitCore                           0x0000000103f20ee1 -[UITabBarController _hideBarWithTransition:isExplicit:duration:] + 1058
    3   UIKitCore                           0x0000000103f564c5 -[UINavigationController _hideOrShowBottomBarIfNeededWithTransition:] + 975
    4   UIKitCore                           0x0000000103f55484 -[UINavigationController pushViewController:transition:forceImmediate:] + 1850
    5   UIKitCore                           0x0000000103f54bac -[UINavigationController pushViewController:animated:] + 681
    6   XXTabBar                            0x0000000100482700 -[FirstViewController gotoNext:] + 208
    7   UIKitCore                           0x00000001045f1204 -[UIApplication sendAction:to:from:forEvent:] + 83
    8   UIKitCore                           0x00
2019-06-20 09:40:08.065555+0800 XXTabBar[10773:55905] 要换了 >
2019-06-20 09:40:08.067366+0800 XXTabBar[10773:55905] ---(
    0   XXTabBar                            0x0000000100481c63 -[UITabBar(FixPush) willMoveToSuperview:] + 83
    1   UIKitCore                           0x0000000104ab70c2 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 545
    2   UIKitCore                           0x0000000103f34ce8 -[UILayoutContainerView addSubview:] + 75
    3   UIKitCore                           0x0000000103f199fc -[UITabBarController _prepareTabBar] + 324
    4   UIKitCore                           0x0000000103f19f3f -[UITabBarController _layoutContainerView] + 378
    5   UIKitCore                           0x0000000103f194a6 -[UITabBarController __viewWillLayoutSubviews] + 38
    6   UIKitCore                           0x0000000103f3438d -[UILayoutContainerView layoutSubviews] + 217
    7   UIKitCore                           0x0000000104abd9c1 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1417
    8   QuartzCore                          0x000000010602eeae -[CALayer layoutSublayers] + 173

从时间上看,TabBar更换到UILayoutContainerView父窗体, 异常情况下比正常情况下提早了0.5秒,且触发的对象也不同。
本来的想法是阻止这种异常的触发,但尝试了很多次都没有找到合适的办法,
那就只能在改变UITabBar的frame了,这样只能效果上基本接近。

通过替换UITabBar的setFrame方法,捕获到异常时的frame

2019-06-20 15:50:48.973860+0800 XXTabBar[34846:368452] frame {{0, 813}, {414, 83}}
2019-06-20 15:50:48.975635+0800 XXTabBar[34846:368452] frame {{0, 813}, {414, 49}}
2019-06-20 15:50:48.975828+0800 XXTabBar[34846:368452] frame {{0, 847}, {414, 49}}
2019-06-20 15:50:49.504939+0800 XXTabBar[34846:368452] frame {{0, 847}, {414, 49}}

正常时

2019-06-20 15:51:17.453253+0800 XXTabBar[34846:368452] frame {{0, 813}, {414, 83}}
2019-06-20 15:51:17.968698+0800 XXTabBar[34846:368452] frame {{0, 813}, {414, 83}}

看到frame中高度和Top都不对,那就校正吧

if (firstArgv.size.height != kIPhoneXTabbarHeight) {
                        firstArgv.size.height = kIPhoneXTabbarHeight;
                    }
                    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];

                    CGFloat y =window.bounds.size.height -kIPhoneXTabbarHeight;
                    if (firstArgv.origin.y != y) {
                        firstArgv.origin.y = y;
                    }

试一下效果:


UITabBar在iPhoneX等Push时显示错乱问题_第2张图片
修改后效果

github 代码位置 https://github.com/yonglinwang002/UITabBar-FixForPush

好了,先这样吧。

以后有时间,再看一下有没有办法改到和正常情况下一样

参考资料:

  1. tabbar 跳动
  2. iOS12.1 在 pop 后,会引起TabBar布局异常
  3. QMUI_iOS 解决iOS12.1 TabBar问题

你可能感兴趣的:(UITabBar在iPhoneX等Push时显示错乱问题)