Runtime实战之定制TabBarItem大小
方案一:UIEdgeInsets
适用场景:
-
适合APP的TabBarItemImage的图片资源放在本地
-
图片超出tabbar的高度,需移动其位置,来进行适应
弊端:
若在本地配置好后,tabbar的图片就不能改动了,若tabbar的图片来自服务端,且不停的切换图片的大小,以上则很难满足。若有此方面的需求请看方案二。
实现:
1 | [tabbarItem setImageInsets:UIEdgeInsetsMake(< #CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)] |
注:图片太大超出tabbar时,系统并不会调整image和title的位置,你需要根据图片的高度,计算出需要往上移动的高度,然后设置top和bottom属性即可。切记top = - bottom,否则image将会被拉伸或者被压缩。
方案二:Runtime
利用runtime的话相对方案一来说要比较复杂一点,但其灵活度比较高,我们能够根据服务端所给的image来动态的变化TabBarItem的大小,类似像淘宝、京东活动时。思想:主要是利用runtime对UITabBar的layoutSubviews进行重写,然后调整UITabBarItem的位置。另外,当时在做的APP已经有4-5年的历史了,一开始打算自已定制tabbar,发现要改动的还是挺多的,于是就放弃了。做之前也看了前辈iOS程序犭袁的CYLTabBarController,从中也学到了不少思路。
实现:
-
首先我们使用runtime method swizzling交换系统的- (void)layoutSubviews;
-
使用KVC对系统的UITabBarButton、UITabBarSwappableImageView、UITabBarButtonLabel、_UIBadgeView进行捕获
-
拿到控件后我们对其的frame进行计算,判断当前有没有超出tabbar的高度,若超出则进行处理
-
再次利用runtime method swizzling交换系统的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;使图片超过后也能接受点击
代码:
-
method swizzling:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | static void ExchangedMethod(SEL originalSelector, SEL swizzledSelector, Class class ) { Method originalMethod = class_getInstanceMethod( class , originalSelector); Method swizzledMethod = class_getInstanceMethod( class , swizzledSelector); BOOL didAddMethod = class_addMethod( class , originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod( class , swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } |
-
计算frame,并对其重新布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | UIView *tabBarImageView, *tabBarButtonLabel, *tabBarBadgeView; for (UIView *sTabBarItem in childView.subviews) { if ([sTabBarItem isKindOfClass:NSClassFromString(@ "UITabBarSwappableImageView" )]) { tabBarImageView = sTabBarItem; } else if ([sTabBarItem isKindOfClass:NSClassFromString(@ "UITabBarButtonLabel" )]) { tabBarButtonLabel = sTabBarItem; } else if ([sTabBarItem isKindOfClass:NSClassFromString(@ "_UIBadgeView" )]) { tabBarBadgeView = sTabBarItem; } } NSString *tabBarButtonLabelText = ((UILabel *)tabBarButtonLabel).text; CGFloat y = CGRectGetHeight(self.bounds) - (CGRectGetHeight(tabBarButtonLabel.bounds) + CGRectGetHeight(tabBarImageView.bounds)); if (y < 3) { if (!tabBarButtonLabelText.length) { space -= tabBarButtonLabelHeight; } childView.frame = CGRectMake(childView.frame.origin.x, y - space, childView.frame.size.width, childView.frame.size.height - y + space); } |
-
让图片超出部分也能响应点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - (UIView *)s_hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.clipsToBounds && !self.hidden && self.alpha > 0) { UIView *result = [ super hitTest:point withEvent:event]; if (result) { return result; } else { for (UIView *subview in self.subviews.reverseObjectEnumerator) { CGPoint subPoint = [subview convertPoint:point fromView:self]; result = [subview hitTest:subPoint withEvent:event]; if (result) { return result; } } } } return nil; } |
注意事项
-
在给tabbar设置图片的时候一定要设置图片的renderingMode,否则就会出现下图中图片丢失的现象
-
UITabBarButton被修改frame之后,仅有UITabBarSwappableImageView能够响应点击事件,不过我们能够在UITabBar的- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;方法中捕获到
-
当适配图片后不要忘记适配_UIBadgeView的frame
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· 程序员常用高效实用工具推荐,办公效率提升利器!
· C#/.NET/.NET Core技术前沿周刊 | 第 23 期(2025年1.20-1.26)