問題描述
看圖說話吧,用慢動(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í)間,再看一下有沒有辦法改到和正常情況下一樣
參考資料: