You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:35:13