一、前言

  • 一次比较懒的想法,不想自定义UITabBar,也不想用第三方框架,于是想尝试修改苹果私有类来达到部分效果

  • 效果如下

    • 点击tabBar 上的按钮,图片有变大再变小的动画
    • tabBar 上某个按钮(购物车按钮),点击弹出控制器是modal出来的,并不属于UITabBarController管理的
    • 上述购物车按钮,是后面加到tabBar 上的自定义的按钮UIButton,本身没有右上角的badge的提示,懒得话,就用

      系统UITabBarButton 的badge
    • 点击商品的加号按钮,实现tabBar上购物车按钮 显示的badge的数字的改变,并实现变大变小的动画

二、相关说明

  • 在swift3及xcode8 beta2 环境下

  • 运行时遍历出成员变量


var count:UInt32 = 0
let ivarlist = class_copyIvarList(NSClassFromString("UITabBarButton")!, &count) for index in 0..<numericCast(count) {
let ivar = ivarlist![index]
let ivarStr = String.init(utf8String: ivar_getName(ivar!))
print("\(ivarStr)")
}
  • 1、UITabBarButton

Optional("_hitRect")
Optional("_info")
Optional("_vibrancyEffectView")
Optional("_label")
Optional("_badge")
Optional("_selectedIndicator")
Optional("_selected")
Optional("_infoInsets")
Optional("_selectedInfoOffset")
Optional("_infoOffset")
Optional("_customSelectedIndicatorImage")
Optional("_labelOffset")
Optional("_buttonTintColorsForState")
Optional("_contentTintColorsForState")
Optional("_badgeColor")
Optional("_badgeTextAttributesForState")
Optional("_showsHighlightedState")
Optional("_centerAllContents")
Optional("_appearanceGuideClass")
Optional("_tabBar")
  • 其中 "_info" ,"_label" ,"_badge" 这三个是我们需要的

    • "_info"是 UIImageView 就是显示的图片,"_label"是UILabel 显示的文字
  • 2、"_badge"是什么呢?它真实类型是什么,子控件及属性名是什么



let tab = window?.rootViewController as! TabBarViewController
for view in tab.tabBar.subviews {
if view.isKind(of: NSClassFromString("UITabBarButton")! ) {
let badgeView = view.value(forKeyPath: "_badge")
print("1\(badgeView!.self)")
let badge = badgeView as! UIView
print("2\(badge.subviews)")
}
}
}


1 <_UIBadgeView: 0x7fd671c336c0; frame = (33 2; 18 18); text = '1'; userInteractionEnabled = NO; layer = <CALayer: 0x608000031240>>
2 [<UIImageView: 0x7fd671c09570; frame = (0 0; 18 18); opaque = NO; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 1 0.231373 0.188235 1; layer = <CALayer: 0x60800002e9a0>>, <UILabel: 0x7fd671c33ba0; frame = (6 1; 6 16); text = '1'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009c890>>]


var count:UInt32 = 0
let ivarlist = class_copyIvarList(NSClassFromString("_UIBadgeView")!, &count)
for index in 0..<numericCast(count) {
let ivar = ivarlist![index]
let ivarStr = String.init(utf8String: ivar_getName(ivar!))
print("badge= \(ivarStr)")
} badge= Optional("_label")
badge= Optional("_background")
badge= Optional("_mergedTextAttributes")
badge= Optional("_text")
badge= Optional("_textAttributes")
badge= Optional("_backgroundColor")

三、做法及代码

  • 自定义TabBarController里,添加4个子控制器,其中第三个添加一个空的UIViewController,图片为UIImage(), 空的TabBarItem,只是让这个系统生成的UITabBarButton占好位置

  • 自定义UIButton,添加到TabBar上,盖住上述 空的UITabBarButton上

  • appDelegate成为TabBarController的代理,监控tabBar的点击



extension AppDelegate: UITabBarControllerDelegate {

    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
for view in tabBarController.tabBar.subviews {
if view.isKind(of: NSClassFromString("UITabBarButton")! ) {
// UITabBarButton是继承UIControl的, 通过 按钮的状态 才判别是否是现在点击的按钮
let state = view.value(forKeyPath: "highlighted")
let stateValue = state as! Int
if stateValue == 1 {
// 拿到当前点击的 按钮的imageView
let imageView = view.value(forKeyPath: "_info")
guard let temp = imageView else {
return
} let tabButtonImgView = temp as! UIImageView
// 添加动画
UIView.animate(withDuration: 0.2, animations: {
tabButtonImgView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}, completion: { (Bool) in
tabButtonImgView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
}
} } }
  • 由于UITabBarButton的子控件都是懒加载的,需要用控制器的tabBarItem 模型对UITabBarButton进行设置

    • 点击商品加号按钮,拿到 控制器tabBarItem,设置badgeValue ,然而购物车按钮(自己添加的) 盖住了部分 系统生成的badgeView,需要调整badgeView的位置


for button in tabBar.subviews {

            if button.isKind(of: NSClassFromString("UITabBarButton")! ) {
let label = button.value(forKeyPath: "_label") as! UILabel
// print("\(label.text)")
if label.text == "" { // 通过label.text 确定是购物车按钮 那位置的 UITabBarButton let badgeButton = button.value(forKeyPath: "_badge")
// print("\(badgeButton!.self)")
guard let badgeView = badgeButton else {
return
} let badge = badgeView as! UIView
// print("\(badge.subviews)")
badge.frame.origin.x = 52 // 可根据需要计算
}
}
}
  • 点击商品加号按钮实现系统 badgeView的动画


let tab = window?.rootViewController as! TabBarViewController

        for button in tab.tabBar.subviews {

            if button.isKind(of: NSClassFromString("UITabBarButton")! ) {
let label = button.value(forKeyPath: "_label") as! UILabel
// 外界通过 lable.text 区别出是我们需要的 第三个UITabBarButton
if label.text == "" {
let badgeButton = button.value(forKeyPath: "_badge")
guard let badgeView = badgeButton else {
return
}
// 拿到 badge 控件
let badge = badgeView as! UIView
// num变量为 点击加号按钮, 商品个数 计量
let str:NSString = NSString.init(format: "%d", num) // kvc 设置属性
badgeView.setValue(UIFont.systemFont(ofSize: 9), forKeyPath: "_label.font")
badgeView.setValue(str, forKeyPath: "_label.text") let tabLabal = badgeView.value(forKeyPath: "_label")
let label = tabLabal as! UILabel
// 拿到lebel 计算label 的大小、尺寸
let size = str.size(attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 9)])
label.frame = CGRect(x: (badge.frame.size.width - size.width) * 0.5, y: (badge.frame.size.height - size.height) * 0.5, width: size.width, height: size.height)
// 添加核心动画
let scaleAni = CABasicAnimation()
scaleAni.keyPath = "transform.scale"
scaleAni.fromValue = 1.0
scaleAni.toValue = 1.2
scaleAni.autoreverses = true
scaleAni.duration = 0.25
badge.layer.add(scaleAni, forKey: nil) }
}
}

四、 问题

  • 刚学习swift 3.0, 各种不熟,各种不顺,加上xcode8 beta版各种不稳定及bug,这次仅仅是尝试swift3的改变而练习的
  • 系统UITabBarButton 里面的子控件都是懒加载的,没有先设置tabBarItem模型 ,用kvc 是赋不上值得,这点得注意

五、参考的 小demo

利用kvc对UITabBar上的UITabBarButton的尝试修改.md的更多相关文章

  1. Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题 swfUpload多文件上传

    Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题(转) 我们都知道普通的文件上传是通过表单进行文件上传的,还不能达到异步上传的目的.通过使用某些技术手段,比如jqu ...

  2. selenium+java利用AutoIT实现文件上传

    转自https://www.cnblogs.com/yunman/p/7112882.html?utm_source=itdadao&utm_medium=referral 1.AutoIT介 ...

  3. 利用SQLite在android上实现增删改查

    利用SQLite在android上实现增删改查 方法: 一.直接利用database.execSQL()方法输入完整sql语句进行操作 这种方法适用于复杂的sql语句,比如多表查询等等 这里适合于增删 ...

  4. 利用SQLite在android上创建数据库

    利用SQLite在android上创建数据库 方法: 1.创建我们的数据库类继承SQLiteOpenHelper类 完成相关函数的重写和数据库对象的初始化 public MySQLiteOpenHel ...

  5. 利用Tengine在树莓派上跑深度学习网络

    树莓派是国内比较流行的一款卡片式计算机,但是受限于其硬件配置,用树莓派玩深度学习似乎有些艰难.最近OPENAI为嵌入式设备推出了一款AI框架Tengine,其对于配置的要求相比传统框架降低了很多,我尝 ...

  6. [转]利用 Commons-Fileupload 实现文件上传

    转载 Java Web开发人员可以使用Apache文件上传组件来接收浏览器上传的文件,该组件由多个类共同组成,但是,对于使用该组件来编写文件上传功能的Java Web开发人员来说,只需要了解和使用其中 ...

  7. 利用TortoiseGit向Github上传文件

    利用TortoiseGit向Github上传文件 第一步:建一个新文件夹,作为本地仓库 第二步:右键选择设置为版本库 若弹出,确认即可 重新打开改文件,会发现多了一个绿色的小勾 在文件夹中会自动生成一 ...

  8. php文件上传大小限制的修改方法大全

    php文件上传大小限制的修改方法大全 基本就是修改maxsize选项,当然为了提高上传文件的成功率,还需要设置超时时间等. 文章如下: [php文件上传]php文件上传大小限制修改,phpmyadmi ...

  9. 【转】利用 three.js 开发微信小游戏的尝试

    前言 这是一次利用 three.js 开发微信小游戏的尝试,并不能算作是教程,只能算是一篇笔记吧. 微信 WeChat 6.6.1 开始引入了微信小游戏,初期上线了一批质量相当不错的小游戏.我在查阅各 ...

随机推荐

  1. 微信浏览器如何禁止iPhone手机上下滑动网页

    代码: /*去掉iphone手机滑动默认行为*/ $('body').on('touchmove', function (event) { event.preventDefault(); });

  2. 一步步学Mybatis-实现多表联合查询(4)

    上一章节中我们已经完成了对单表的CRUD操作,接下来今天这一讲讲述的是关于Mybatis在多表查询时候的应用,毕竟实际业务中也是多表的联合查询比较多嘛~ 还记得最一开始我们新建过一张Website表吗 ...

  3. MyEclipse7.0破解下载

    MyEclipse7.0 下载地址:downloads.myeclipseide.com/downloads/products/eworkbench/7.0M1/MyEclipse_7.0M1_E3. ...

  4. 腾讯微博java(android)sdk新增微博api详细介绍

    本文主要介绍腾讯微博android sdk中新增微博有关的8个接口,以及使用的示例代码 注意:以下所有的api示例代码都需要先新建QqTSdkService类对象qqTSdkService并初始化,见 ...

  5. 关于SPA及RPA

    SPA Request-------->Callback (page onload,execute <script>)-------->call func RPA URL re ...

  6. 【JavsScript】webapp的优化整理

    单页or多页 webapp 现状 优劣之分 网络传输优化 综述 fake页-首屏加速 降低请求数 降低请求量 缓存Ajax/localstorage DOM操作优化 综述 关于页面渲染 减少使用定位属 ...

  7. 使用ServiceStackRedis链接Redis简介 [转]

      注:关于如何在windows,linux下配置redis,详见这篇文章:)          Discuz!NT中的Redis架构设计 目前网上有一些链接Redis的C#客户端工具,这里介绍其中也 ...

  8. js正則表達式语法

    1. 正則表達式规则 1.1 普通字符 字母.数字.汉字.下划线.以及后边章节中没有特殊定义的标点符号,都是"普通字符".表达式中的普通字符,在匹配一个字符串的时候,匹配与之同样的 ...

  9. Android(java)学习笔记67:多线程程序练习

    需求: 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票. 两种方式实现 A:继承Thread类 B:实现Runnable接 1. 首先我们利用方式 ...

  10. Oracle基础 (十一)字符串函数

    一.字符串函数 LENGTH(char1,char2) SELECT LENGTH('abc def gh') FROM dual; --获取字符串的长度,包含空格 结果: CONCAT(char1, ...