news 2026/10/15 13:23:49

可滚动的标签栏:SDSegmentedControl如何用UIScrollView自动跟随选中的分段

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
可滚动的标签栏:SDSegmentedControl如何用UIScrollView自动跟随选中的分段

【免费下载链接】SDSegmentedControl

A drop-in remplacement for UISegmentedControl that mimic iOS 6 AppStore tab controls

项目地址:https://gitcode.com/gh_mirrors/sd/SDSegmentedControl
点击查看免费下载

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;

两个信息量很大的点:

  1. 它直接继承自UISegmentedControl——所以selectedSegmentIndex、UISegmentedControlValueChanged事件、IB 的 segments 配置全部照用,属于真正的"drop-in replacement"(直接替换)。
  2. 它实现了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];

三个关键设计:

  1. scrollRectToVisible:targetRect:让目标分段(外加半份边距)完整进入可视区,并带动画。滚动到哪里由系统计算,开发者不用手算contentOffset。
  2. _isScrollingBySelection标志位:标记"这次滚动是选择分段引起的"。它的作用是在程序化滚动期间屏蔽 delegate 回调对高亮位置的干扰,避免两套逻辑打架。
  3. 在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 示例

  1. 把 SDSegmentedControl.h 和 SDSegmentedControl.m 拖入工程
  2. 在Build Phases → Link Binary With Libraries里加入QuartzCore
  3. 像使用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

项目地址:https://gitcode.com/gh_mirrors/sd/SDSegmentedControl
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 13:23:16

Docker架构深度解析:从docker run到容器运行原理

1. 从一次容器启动异常说起&#xff1a;为什么架构图比命令更重要很多人第一次接触Docker&#xff0c;都是从docker run这条命令开始的。敲下去&#xff0c;容器跑起来了&#xff0c;感觉这东西真方便。但等到某天容器启动失败、端口映射不生效、数据莫名其妙丢了&#xff0c;再…

作者头像 李华
网站建设 2026/10/15 13:23:15

Android context-mode 实战:ActionMode/CAB 批量操作完整指南

在不同技术圈子里搜 context-mode&#xff0c;出来的东西五花八门&#xff0c;有人拿它指代码编辑器里的上下文感知模式&#xff0c;也有人用它描述模型对话的上下文窗口。但如果你在做 Android 客户端&#xff0c;context-mode 大概率指同一个东西——上下文操作模式&#xff…

作者头像 李华
网站建设 2026/10/15 13:23:11

SQLite的context-mode:MCP协议下的上下文感知检索机制

1. “context-mode”不是新功能&#xff0c;而是MCP协议落地时绕不开的上下文协商机制最近在多个技术社区和开源项目讨论区里&#xff0c;“context-mode”这个词频繁出现在SQLite、FTS5、BM25相关话题的交叉地带——它既不挂在任何主流框架的文档首页&#xff0c;也不作为独立…

作者头像 李华
网站建设 2026/10/15 13:21:53

从单帧到超帧:多帧降噪、计算摄影与工程实战解析

第一次看到 hyperframes 这个词的人&#xff0c;大概率会有点懵。帧就帧&#xff0c;怎么还超帧&#xff1f;这个词在搜索指数上悄悄涨起来&#xff0c;背后其实不是某个新软件、新产品突然改名&#xff0c;而是一种思路正在被越来越多领域默认采用&#xff1a;单帧不再是唯一的…

作者头像 李华
网站建设 2026/10/15 13:20:46

SAP成本中心与内部订单:核心区别、配置逻辑与实操避坑指南

1. 成本中心与内部订单到底在解决什么问题刚接触SAP财务模块的朋友&#xff0c;十有八九会在成本中心和内部订单这两个概念上卡住。我第一次配这两个东西的时候&#xff0c;盯着屏幕看了半天&#xff0c;心里想的是&#xff1a;不都是归集费用吗&#xff0c;搞两套东西出来不是…

作者头像 李华
网站建设 2026/10/15 13:20:39

PHP自主开发二维码生成方案与优化实践

1. 项目背景与核心价值去年指导学弟毕业设计时&#xff0c;发现他花了两周时间对接某商业二维码API&#xff0c;结果在答辩前一天接口突然限流&#xff0c;导致演示环节直接卡壳。这种依赖第三方服务的风险&#xff0c;正是我们自主开发二维码生成系统的意义所在。基于PHP的二维…

作者头像 李华