现在我们使用的APP框架,大部分都是
UINavigationController + UITabBarController
的模式。如果使用的是默认的UITabBar
磨砂效果,而且在pushViewController
时,设置了hidesBottomBarWhenPushed
属性为true
,则在iOS 12.1系统,使用手势返回的时候,就会触发UITabBarButton
被设置frame.size
为 (0, 0)的错误布局, 导致的tabBar
上的图标和文字偏移错位。
extension UINavigationController {
@objc open func ck_pushViewController(_ viewController: UIViewController, animated: Bool) {
viewController.hidesBottomBarWhenPushed = true
self.ck_pushViewController(viewController, animated: animated)
if self.viewControllers.count == 1 {
viewController.hidesBottomBarWhenPushed = false
}
}
}
方案一:修改tabBar
的isTranslucent
透明度(不推荐)或添加背景图片
1、可以通过修改tabBar
的透明度达到我们想要的效果:
if #available(iOS 12.1, *) {
UITabBar.appearance().isTranslucent = false
} else {
UITabBar.appearance().isTranslucent = true
}
但是这个会引入其它的问题:
- (1)
tabBar
想保留原来的磨砂效果; - (2)会影响
tableView
的布局,底部间距多了tabBar的高度;
所以这种方式pass
。
2、可以统一给tabBar
添加背景图片,则不会有这个问题。
方案二:使用runtime
过滤UITabBarButton
的frame
设置(推荐)
为了达到既不修改原先的设计又达到解决这个问题,我这边通过交换UITabBarButton
的setFrame
方法,过滤一些错误的大小设置,来达到我们的目的,这里提供了OC
和Swift
两个版本的代码,如下:
-
OC
版
添加
UIView
的分类,并在+load
方法中,交换UITabBarButton
的setFrame
方法,过滤错误的大小
#import <UIKit/UIKit.h>
@interface UIView (CKTabBarItem)
@end
#import "UIView+CKTabBarItem.h"
#import <objc/runtime.h>
@implementation UIView (CKTabBarItem)
+ (void)load
{
if (@available(iOS 12.1, *)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = NSClassFromString(@"UITabBarButton");
SEL originalSelector = @selector(setFrame:);
SEL swizzledSelector = @selector(ck_setFrame:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL isSuccess = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (isSuccess) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
}
- (void)ck_setFrame:(CGRect)frame
{
if (!CGRectIsEmpty(self.frame)) {
if (CGRectIsEmpty(frame)) {
return;
}
frame.size.height = MAX(frame.size.height, 48.0);
}
[self ck_setFrame: frame];
}
@end
-
Swift
版
Swift4 不支持dispatch_once,静态变量默认用dispatch_once初始化,可以替代dispatch_once的实现
import UIKit
extension UIView {
/// Swift4 不支持dispatch_once,静态变量默认用dispatch_once初始化,可以替代dispatch_once的实现
private static let swizzlingTabBarButtonFrame: Void = {
guard #available(iOS 12.1, *) else {
return
}
guard let cls = NSClassFromString("UITabBarButton") else {
return
}
let originalSelector = #selector(setter: UIView.frame)
let swizzledSelector = #selector(UIView.ck_setFrame)
guard let originalMethod = class_getInstanceMethod(cls, originalSelector) else {
return
}
guard let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector) else {
return
}
let isSuccess = class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod))
if (isSuccess) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}()
@objc func ck_setFrame(frame: CGRect) {
var newFrame: CGRect = frame
if !self.frame.isEmpty {
guard !newFrame.isEmpty else {
return
}
newFrame.size.height = newFrame.size.height > 48.0 ? newFrame.size.height : 48.0
}
self.ck_setFrame(frame: newFrame)
}
open class func swizzledTabBarButtonFrame() {
UIView.swizzlingTabBarButtonFrame
}
}
Swift
因为没有+load
方法,所以需要手动去触发,我这里直接放到UIApplicationDelegate
的代理方法中。
// MARK: - <UIApplicationDelegate>
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ...
// 处理iOS 12.1系统tabBar图标偏移错位问题
UIView.swizzledTabBarButtonFrame()
return true
}
如果有任何疑问,欢迎留言或私信交流。