问题描述
看图说话吧,用慢动作,看得明显一些
先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;
}
试一下效果:
github 代码位置 https://github.com/yonglinwang002/UITabBar-FixForPush
好了,先这样吧。
以后有时间,再看一下有没有办法改到和正常情况下一样
参考资料:
- tabbar 跳动
- iOS12.1 在 pop 后,会引起TabBar布局异常
- QMUI_iOS 解决iOS12.1 TabBar问题