UITabBar在iPhoneX等Push時(shí)顯示錯(cuò)亂問題

問題描述

看圖說話吧,用慢動(dòng)作,看得明顯一些


跳一下

先Present,再Push,看到TabBar 跳了一下,真機(jī)上有時(shí)會(huì)不跳,就顯示一半的情況。

解決方案

直接上解決方案:
替換 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);
            };
        });
    });
}

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

分析過程

起初并不知道這一問題重現(xiàn)的步驟,一直以為是偶現(xiàn)的,沒注意,后來發(fā)現(xiàn)這個(gè)問題次數(shù)很多,就開始關(guān)注了。

重現(xiàn)步驟:

APP里面有UITabBar,每個(gè)Tab頁里都有一個(gè)Nav,并且在這個(gè)頁面上先Present一個(gè)頁后,關(guān)閉Present的View,再Push到新的頁面,就會(huì)出現(xiàn)這個(gè)現(xiàn)象。
還有幾個(gè)前提條件:
1、iPhoneX 系列
2、TabBar的translucent屬性為 YES,即半透明磨砂狀的
3、新的頁面的hidesBottomBarWhenPushed 是 YES的,即要TabBar要消失
4、Push時(shí),一定是使用動(dòng)畫效果的(這是個(gè)廢話)
這么多條件下才能出現(xiàn),可見這個(gè)Bug應(yīng)該不算大概率的Bug了。

首先,知道了重現(xiàn)的步驟,那就比較一下Present一個(gè)View后,TabBar具體發(fā)生了什么變化。
從兩次的表現(xiàn)來看,我們發(fā)現(xiàn)
在異常情況下 Push時(shí) TabBar是到進(jìn)入頁面后,才消失的。
而正常情況下,Push時(shí),TabBar一直跟隨第一個(gè)頁面,感覺這兩種情況下TabBar的父窗體都不同的樣子。

那就從堆??匆幌?,UIView有一個(gè)方法 - (void)willMoveToSuperview:(nullable UIView *)newSuperview 可以捕獲到父窗體變更,我們用個(gè)Category實(shí)現(xiàn)一下UITabBar的這個(gè)方法,看一下變更時(shí)的堆棧
參考代碼:

@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] 要換了 <UIView: 0x7fb15801d210; frame = (414 0; 414 896); layer = <CALayer: 0x60000197e9a0>>
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] 要換了 <UILayoutContainerView: 0x7fb158016410; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x60000197d4c0>>
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] 要換了 <UIView: 0x7fb15801d210; frame = (0 0; 414 896); layer = <CALayer: 0x60000197e9a0>>
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] 要換了 <UILayoutContainerView: 0x7fb158016410; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x60000197d4c0>>
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

從時(shí)間上看,TabBar更換到UILayoutContainerView父窗體, 異常情況下比正常情況下提早了0.5秒,且觸發(fā)的對(duì)象也不同。
本來的想法是阻止這種異常的觸發(fā),但嘗試了很多次都沒有找到合適的辦法,
那就只能在改變UITabBar的frame了,這樣只能效果上基本接近。

通過替換UITabBar的setFrame方法,捕獲到異常時(shí)的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}}

正常時(shí)

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都不對(duì),那就校正吧

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

試一下效果:


修改后效果

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

好了,先這樣吧。

以后有時(shí)間,再看一下有沒有辦法改到和正常情況下一樣

參考資料:

  1. tabbar 跳動(dòng)
  2. iOS12.1 在 pop 后,會(huì)引起TabBar布局異常
  3. QMUI_iOS 解決iOS12.1 TabBar問題
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容