如何在UITableView中选中分区时修改sectionIndexTitle的颜色
这个需求我之前也帮开发者解决过,系统默认的sectionIndexColor确实只能统一修改所有索引字母的颜色,要实现「滑动时选中字母变红,其余变蓝」的效果,得自定义索引栏才行。另外调整索引栏高度的需求,自定义方案也能轻松搞定。我给你整理了完整的实现步骤和代码:
一、实现选中分区索引字母单独变色
推荐使用自定义索引栏视图的方案,完全基于公开API,没有被App Store拒审的风险,而且灵活性更高。
步骤1:创建自定义索引栏类
这个类会负责渲染索引字母、处理触摸事件、跟踪选中状态并更新颜色:
import UIKit // 定义代理,用来通知ViewController选中了哪个索引 protocol CustomSectionIndexViewDelegate: AnyObject { func sectionIndexView(_ sectionIndexView: CustomSectionIndexView, didSelectIndex index: Int) } class CustomSectionIndexView: UIView { weak var delegate: CustomSectionIndexViewDelegate? // 存储所有索引标签 private var indexLabels: [UILabel] = [] // 当前选中的索引位置 private var selectedIndex: Int? // 外部可配置的索引标题数组 var indexTitles: [String] = [] { didSet { setupIndexLabels() } } // 外部可配置的颜色:正常状态和选中状态 var normalColor: UIColor = .blue var selectedColor: UIColor = .red override init(frame: CGRect) { super.init(frame: frame) setupBaseView() } required init?(coder: NSCoder) { super.init(coder: coder) setupBaseView() } private func setupBaseView() { backgroundColor = .clear isUserInteractionEnabled = true } // 根据索引标题创建并布局标签 private func setupIndexLabels() { // 先清理旧标签 indexLabels.forEach { $0.removeFromSuperview() } indexLabels.removeAll() guard !indexTitles.isEmpty else { return } let labelHeight = bounds.height / CGFloat(indexTitles.count) for (index, title) in indexTitles.enumerated() { let label = UILabel() label.text = title label.textAlignment = .center label.font = UIFont.systemFont(ofSize: 12) label.textColor = normalColor label.tag = index label.frame = CGRect( x: 0, y: CGFloat(index) * labelHeight, width: bounds.width, height: labelHeight ) addSubview(label) indexLabels.append(label) } } // 处理触摸开始、移动事件 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { handleTouch(touches) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { handleTouch(touches) } // 触摸结束后重置选中状态 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { selectedIndex = nil updateLabelColors() } private func handleTouch(_ touches: Set<UITouch>) { guard let touch = touches.first else { return } let touchPoint = touch.location(in: self) // 计算触摸点对应的索引 let index = Int(touchPoint.y / (bounds.height / CGFloat(indexTitles.count))) guard index >= 0, index < indexTitles.count else { return } if selectedIndex != index { selectedIndex = index updateLabelColors() // 通知代理滚动到对应分区 delegate?.sectionIndexView(self, didSelectIndex: index) } } // 更新所有标签的颜色 private func updateLabelColors() { indexLabels.forEach { label in label.textColor = label.tag == selectedIndex ? selectedColor : normalColor } } // 视图布局变化时重新布局标签 override func layoutSubviews() { super.layoutSubviews() setupIndexLabels() } }
步骤2:在ViewController中集成自定义索引栏
把自定义索引栏添加到页面,关联TableView的分区数据,并实现滚动逻辑:
import UIKit class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CustomSectionIndexViewDelegate { private let tableView = UITableView(frame: .zero, style: .plain) // 示例分区索引标题 private let sectionTitles = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] // 自定义索引栏实例 private let customIndexView = CustomSectionIndexView() override func viewDidLoad() { super.viewDidLoad() setupTableView() setupCustomIndexView() // 注册cell tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") } private func setupTableView() { tableView.dataSource = self tableView.delegate = self tableView.frame = view.bounds view.addSubview(tableView) // 隐藏系统默认的索引栏(避免和自定义视图冲突) tableView.sectionIndexColor = .clear } private func setupCustomIndexView() { customIndexView.indexTitles = sectionTitles customIndexView.normalColor = .blue // 未选中字母颜色 customIndexView.selectedColor = .red // 选中字母颜色 customIndexView.delegate = self // 自定义索引栏的位置和大小:右侧,宽度30,高度比TableView矮200(顶部底部各留100间距) let indexViewWidth: CGFloat = 30 let indexViewHeight = tableView.bounds.height - 200 customIndexView.frame = CGRect( x: view.bounds.width - indexViewWidth - 8, // 右侧留8pt边距 y: (tableView.bounds.height - indexViewHeight) / 2, // 垂直居中 width: indexViewWidth, height: indexViewHeight ) view.addSubview(customIndexView) } // MARK: - UITableView DataSource func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 5 // 每个分区5行示例数据 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = "\(sectionTitles[indexPath.section]) - Row \(indexPath.row)" return cell } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } // MARK: - CustomSectionIndexViewDelegate func sectionIndexView(_ sectionIndexView: CustomSectionIndexView, didSelectIndex index: Int) { // 滚动到选中分区的第一行 tableView.scrollToRow(at: IndexPath(row: 0, section: index), at: .top, animated: false) } }
二、调整索引栏高度
在上面的代码中,我们已经通过设置customIndexView.frame的height和y值来控制索引栏的高度和位置:
- 如果想让索引栏顶部留空100pt,可以这样设置frame:
let indexViewHeight = tableView.bounds.height - 100 customIndexView.frame = CGRect( x: view.bounds.width - indexViewWidth - 8, y: 100, // 从顶部100pt开始 width: indexViewWidth, height: indexViewHeight )
- 如果想让索引栏只占中间一部分,比如上下各留150pt,就设置
indexViewHeight = tableView.bounds.height - 300,y = 150即可。
内容的提问来源于stack exchange,提问作者Jorge A Gomez
相关产品推荐
相关产品推荐

