iOS中UITableView是经常使用的控件,当数据量大时,为了方便用户快速查看和检索数据,通常会使用分组和索引栏,而系统的索引栏样式不支持自定义,因此参考QQ音乐的索引栏,结合项目中的使用情况,自定义了一个TableView的索引栏库
QQ音乐的TableView的索引栏的样式如下图:

笔者认为要设计和实现一个功能或者库,首先不必关注实现细节,先分析清楚需求,然后做总体规划和设计。
### 需求分析
参照QQ音乐的索引栏,一个自定义的索引栏有以下需求:
1. 大小和位置、背景颜色支持自定义
2. 支持配置分组数超过一个阈值才显示
3. 支持配置常驻显示或滚动出现停止滚动消失两种风格
4. 索引标题字体和颜色支持自定义
5. 支持配置索引标题高亮颜色
6. 显示和隐藏索引栏动画
6. 指示器样式支持自定义
7. 默认字体和颜色
8. 高亮字体和颜色
9. 代理方法,当用户点击索引栏时通知代理
分析清楚需求后,我们可以开始技术选型,这个UI框架组成比较单一,主要考虑显示和交互两方面。首先很容易想到用TableView来实现这个UI显示,但是仔细一想发现交互上不能满足,TableView主要有点选和滑动两种交互,但是显然索引栏即能点选又能通过手指滑动触发交互,这个跟TableView的滑动滚动内容有冲突,这里内容不需要滚动。因此最后直接选择继承UIView来实现,在touch方法中来处理交互。
### 接口设计
根据需求分析和技术选型,在封装过程中应保证调用方能灵活配置的同时使用简单,设计成和正常的UIView操作一样,提供配置方法,和TableView绑定后,不需要关心索引切换和TableView滑动。接口文件设计如下:
1 | /** |
如代码所示定义了字体、颜色、高亮色、是否圆角等供调用方灵活配置,同时提供常驻显示和滚动显示隐藏两种风格应对不同的需求,调用方可以配置索引的最小显示数量阈值,这些属性都有默认值,当调用方不需要定制时,直接使用即可,只需要配置大小位置和数据源数据即可。
### 实现
笔者建议,针对需求先有大概的设计实现方案,不要急于实现,设计好后再开始编码实现,需求分析和接口设计做好了,代码实现自然会水到渠成。
主要实现难点和思路如下:
1. UI方面使用Label来显示索引标题,并能进行动态的刷新,索引数量改变或选中高亮等
2. 交互方面通过用户的触摸点计算出当前选中的索引,进行索引标题高亮和滚动绑定的TableView到对应的section
3. 监听绑定的TableView的滚动,获取到当前滚动到的section,高亮相应的索引标题
4. 使用滚动显示风格时,监听TableView的滚动,开始滚动执行索引栏显示动画,停止滚动延迟执行索引栏隐藏动画
5. 用户手动操作索引栏时,执行索引栏指示器显示动画,操作完毕延迟执行指示器隐藏动画
首先重写UIView的init和initWithFrame方法,进行属性的基本初始化和指示器子View的初始化工作,指示器用一张背景图和Label实现。
1 | #pragma mark -- init |
然后封装数据刷新方法,当索引栏数组变化时刷新,这里笔者利用数组来做索引标题的缓存,避免重复销毁和创建,同时也方便通过index操作对应的Label,当然其中也做了最小数据量显示阈值的操作。
1 | #pragma mark -- 刷新视图 |
由于self的frame在layoutSubView方法时才唯一确定,因此索引标题的布局放在该方法中实现,如果在init方法或者reload方法可能出现不准确的情况,这也是笔者觉得自定义View时可以注意的地方。根据数据源数组长度计算每一个Label的frame,并且更新索引栏的高度,保持居中显示,还会根据是否切圆角属性进行切圆角。
1 | #pragma mark -- 布局 |
到这里,其实UI层已经基本实现了,该控件UI其实比较简单,接下来是交互部分。交互主要是通过重写touchesBegan和touchesMove方法,获取到用户触摸到的点,然后计算出当前用户选中的索引栏标题。同时重写touchesEnded和touchesCancelled来处理视图的隐藏操作。
1 | #pragma mark -- events |
关键方法是处理用户选中操作,这里封装了一个方法didSelectRowIndex,首先直接通过index获取到索引标题Label,设置高亮颜色,然后index转换成indexPath,执行绑定的TableView的滚动,通过Label的中心Y值修改指示器视图位置并通知代理即可。为了避免用户触摸同一个索引标题范围反复执行的问题,保存了当前选中的currentIndex,比较不同才执行。
1 | /** |
另一个交互是监听绑定的TableView的滚动执行相应的选中索引操作和显示隐藏索引栏操作,如何监听滚动和停止滚动开始笔者没有找到好的方式。最开始的方案是通过暴露三个方法需要调用方调用,调用方通过ScrollView代理获取到滚动相关方法再传递给索引栏,再通过绑定的TableView获取到当前可见的cell的indexpath的第一个获取到section,即可获取到当前需要高亮的index。这种方式不太优雅,调用方使用复杂。最后发现可以通过KVO监听contentOffset配合ScrollView的三个属性isTracking、isDragging、isDecelerating知道TableView的手势出发的滚动和暂停,完美解决该问题,调用方使用更简单。
1 | #pragma mark -- kvo |
实现完上面的交互后,该索引栏基本已经实现完了。显示隐藏索引栏和显示隐藏指示器视图只是最简单的透明度UIView动画,这里就不贴代码了,读者有兴趣可以直接看源码,其中只需要注意延迟执行隐藏动画,并在显示前取消隐藏操作即可。
源码地址为:GWTableViewIndexBar
最后的效果图如下:
