【免费下载链接】SDSegmentedControl
A drop-in remplacement for UISegmentedControl that mimic iOS 6 AppStore tab controls
SDSegmentedControl 是一个可替换UISegmentedControl的 iOS 分段标签栏控件,支持内容自适应宽度、分段过多时横向滚动,并在切换选中项时让标签栏自动滚动跟随。本文带你看懂它的实现原理,以及如何在项目里快速上手这个可滚动的分段标签栏。
为什么需要可滚动的分段标签栏?
原生的UISegmentedControl有个经典痛点:分段数量一多,标题就会被压缩得面目全非。而很多真实场景(比如 AppStore 式分类页、多分类切换)恰恰需要"分段数量多、每个分段按内容取宽度、放不下就滚动"的效果。
SDSegmentedControl 正是为此设计的,它的核心特性(详见 README.md):
- 分段可以是纯文字、纯图片或图文组合
- 内容感知的动态宽度:每个分段多宽取决于内容,也可单独指定
- 分段过多时整体可横向滚动
- 选中的高亮胶囊和箭头会平滑动画到新位置
- 支持 Interface Builder:拖一个
UISegmentedControl进画板,把类改成SDSegmentedControl即可 - 支持
UIAppearance全局换肤、UIControl事件回调
内部结构:一个装了 UIScrollView 的 UISegmentedControl
在 SDSegmentedControl.h 中可以看到类的声明:
@interface SDSegmentedControl : UISegmentedControl <UIScrollViewDelegate> @property (assign,nonatomic) UIScrollView *scrollView;两个信息量很大的点:
- 它直接继承自
UISegmentedControl——所以selectedSegmentIndex、UISegmentedControlValueChanged事件、IB 的 segments 配置全部照用,属于真正的"drop-in replacement"(直接替换)。 - 它实现了
UIScrollViewDelegate,内部持有一个UIScrollView,所有分段按钮(SDSegmentView,继承自UIButton)都铺在这个 scrollView 上。
初始化时(SDSegmentedControl.m 的commonInit)会做三件事:
- 清空父类自带的子视图,改用自绘的
CAShapeLayer画背景与底部分割线 - 新建一个透明背景、隐藏滚动条的
UIScrollView加进来 - 在 scrollView 上放一个
SDStainView(选中态的高亮"胶囊")
也就是说,你看到的标签栏本质是:外壳(背景/边框/箭头)固定在视图上,内容(分段按钮 + 高亮胶囊)滚动在 UIScrollView 里。
滚动内容宽度是如何算出来的?
核心逻辑在layoutSegments方法里(SDSegmentedControl.m):
- 先把所有分段按钮的实际宽度加起来,再加上
interItemSpace(分段间距,默认 30pt) - 如果总宽超过控件宽度:给内容区两端各加 20pt 的
kSDSegmentedControlScrollOffset边距,contentSize.width设为这个加宽后的总宽——scrollView 由此变得可滚动 - 如果总宽放得下:
contentSize就等于控件宽度,分段整体居中,不产生滚动
所以"是否可滚动"是根据内容自动判断的,不需要你手动配置。
选中后自动滚动跟随的原理
这是最有意思的部分。当selectedSegmentIndex变化时,layoutSegments会检查高亮胶囊(stain view)是否已被当前滚动位置"挤出"可视区域(SDSegmentedControl.m):
_isScrollingBySelection = animated; [self.scrollView scrollRectToVisible:targetRect animated:animated];三个关键设计:
scrollRectToVisible:targetRect:让目标分段(外加半份边距)完整进入可视区,并带动画。滚动到哪里由系统计算,开发者不用手算contentOffset。_isScrollingBySelection标志位:标记"这次滚动是选择分段引起的"。它的作用是在程序化滚动期间屏蔽 delegate 回调对高亮位置的干扰,避免两套逻辑打架。- 在
scrollViewDidEndScrollingAnimation中复位标志位(SDSegmentedControl.m):滚动动画结束,控制权交还给用户手势。
这样就形成了一个干净的闭环:选中 → 滚动到目标 → 动画结束 → 恢复手动滚动。
手动滑动时,高亮和箭头为什么不乱跑?
反过来,当用户自己拖动标签栏时,scrollViewDidScroll(SDSegmentedControl.m)会实时同步两个视觉元素:
if (_isScrollingBySelection || _selectedSegmentIndex == UISegmentedControlNoSegment) return; // 用"分段中心点 - contentOffset.x"重算箭头路径 [self drawPathsToPosition:selectedItemCenterPosition - scrollView.contentOffset.x animated:NO];- 箭头(底部那个指向选中分段的折线)根据最新偏移重新绘制定位
- 高亮胶囊的
center直接跟随分段中心点,保证它永远"钉"在选中项上,哪怕标签栏正在滑动
而如果是程序触发的选择动画(_isScrollingBySelection == YES),这个回调直接return,把画面交给scrollRectToVisible的动画去演。两种场景互不干扰,这就是标志位存在的意义。
快速上手:5 分钟跑通 Example 示例
- 把 SDSegmentedControl.h 和 SDSegmentedControl.m 拖入工程
- 在Build Phases → Link Binary With Libraries里加入
QuartzCore - 像使用
UISegmentedControl一样声明和使用它即可
仓库自带一个完整的 Example 工程(Example/Example/ViewController.m),演示了:
- 动态增删分段(纯文字 / 纯图片 / 图文 / 自定义宽度 4 种分段循环插入)
- 切换选中项时观察标签栏自动滚动 + 高亮与箭头动画跟随
- 通过
UIAppearance一键换肤(背景、边框、箭头大小、字体、高亮颜色) - 开关联动:
arrowSize置 0 隐藏箭头、arrowHeightFactor取反翻转箭头方向
小技巧:在 IB 里直接拖
UISegmentedControl,把 Custom Class 改成SDSegmentedControl,segments 配置会自动被接管(见awakeFromNib的迁移逻辑)。
常用外观参数速查
这些属性都标注了UI_APPEARANCE_SELECTOR,可以通过+[SDSegmentedControl appearance]全局生效,也可以在实例上单独设置(定义见 SDSegmentedControl.h):
| 属性 | 作用 | 默认值 |
|---|---|---|
interItemSpace | 分段之间的间距 | 30pt |
arrowSize/arrowPosition | 箭头大小与上下位置 | 6.5pt / 底部 |
arrowHeightFactor | 箭头高度系数,取反即翻转方向 | 1.0 |
animationDuration | 选中切换动画时长 | 0.2s |
centerSegmentsIfPossible | 放得下时让分段整体居中 | NO |
titleFont/selectedTitleFont | 普通/选中态字体 | 系统字体 |
backgroundColor/borderColor | 标签栏底色与底边线 | 系统灰 |
小结
SDSegmentedControl 用了一个非常克制的组合拳解决"可滚动的分段标签栏"问题:
UIScrollView承载内容,contentSize按分段实际宽度自动计算,多则滚、少则居中scrollRectToVisible实现选中后的自动跟随滚动,不手算偏移_isScrollingBySelection标志位 +UIScrollViewDelegate让"程序滚动"与"手动拖动"两种场景互不打架- 对外仍保持
UISegmentedControl的 API,老项目可无缝替换
适合分段较多、内容宽度不一、又想要顺滑动画的标签栏场景。如果你正在找一个比原生控件更灵活的 iOS 分段控件方案,不妨从 Example 工程开始体验。
【免费下载链接】SDSegmentedControl
A drop-in remplacement for UISegmentedControl that mimic iOS 6 AppStore tab controls
相关推荐
如何实现 React Native Tab View 的可滚动标签栏:终极指南
如何实现 React Native Tab View 的可滚动标签栏:终极指南 想要在 React Native 应用中实现流畅的可滚动标签栏吗?React N
移动开发UI组件一行代码换类即可用:如何在Interface Builder中用SDSegmentedControl打造App Store风格分段标签
一行代码换类即可用:如何在Interface Builder中用SDSegmentedControl打造App Store风格分段标签 SDSegmentedC
如何使用vulkan_best_practice_for_mobile_developers构建高效移动端渲染系统
如何使用vulkan_best_practice_for_mobile_developers构建高效移动端渲染系统 vulkan_best_practice_f
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考