You need to enable JavaScript to run this app. iOS开发:如何在UITableView中按好友姓氏首字母实现通讯录式分区
优惠活动
大模型
产品
解决方案
定价
更多

iOS开发:如何在UITableView中按好友姓氏首字母实现通讯录式分区

按姓氏首字母分组的通讯录式UITableView实现

你之前想到用「字符为键的字典存储分组数据」的思路完全正确,之前卡壳的核心原因是:Swift的字典是无序集合,直接拿字典的键渲染表格分区的话,分区顺序是随机的,只需要补上「排序分区索引」的逻辑,就能实现和系统通讯录完全一致的效果。

核心实现步骤

  • 给控制器新增两个结构化的存储属性,替代之前直接拿平铺数组当数据源的逻辑
  • 先对全量好友按姓氏的字母顺序做全局排序
  • 遍历排序后的好友,按姓氏首字母做字典分组
  • 把所有分组的首字母提取出来,按字符顺序排好成固定数组,作为表格的分区索引
  • 改写所有UITableViewDataSource代理方法,从分组结构里取对应分区、对应行的数据
  • 修正页面跳转时的取值逻辑,避免因为数据源结构调整出现数据错位

1. 新增分组存储属性

// 分组映射:键为分组首字母,值为该组下的好友数组
private var groupedFriends: [Character: [Friend]] = [:]
// 按字母顺序排好的分区标题(首字母)列表,保证分区渲染顺序固定
private var sortedSectionLetters: [Character] = []

2. 改写表格配置方法,嵌入分组逻辑

替换原来的configureTable方法:

private func configureTable() {
    tableView.dataSource = self
    // 第一步:全局按好友姓氏的字母顺序排序全量好友
    friendsArray.sort { friend1, friend2 in
        // 提取姓氏:把全名按空格拆分,最后一段即为姓氏;如果没有空格则用全名兜底
        let lastName1 = friend1.name.components(separatedBy: " ").last ?? friend1.name
        let lastName2 = friend2.name.components(separatedBy: " ").last ?? friend2.name
        return lastName1 < lastName2
    }
    // 第二步:遍历排序后的好友,完成字典分组
    for friend in friendsArray {
        let lastName = friend.name.components(separatedBy: " ").last ?? friend.name
        guard let firstLetter = lastName.first else { continue }
        // 把好友追加到对应首字母的分组数组中
        groupedFriends[firstLetter, default: []].append(friend)
    }
    // 第三步:提取所有分组首字母,按字符顺序排序作为分区列表
    sortedSectionLetters = groupedFriends.keys.sorted()
    // 刷新表格,渲染分组后的数据
    tableView.reloadData()
}

3. 改写UITableView数据源代理

替换原来单分区的代理逻辑,新增分区头、右侧快速索引条的能力,完全对齐系统通讯录的交互:

extension FriendsViewController: UITableViewDataSource {
    // 分区总数 = 排序好的首字母数量
    func numberOfSections(in tableView: UITableView) -> Int {
        sortedSectionLetters.count
    }
    // 每个分区的行数 = 对应分组下的好友数量
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionLetter = sortedSectionLetters[section]
        return groupedFriends[sectionLetter]?.count ?? 0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(
            withIdentifier: FriendViewCell.identifier,
            for: indexPath
        ) as? FriendViewCell else { return UITableViewCell() }
        // 先取当前分区对应的首字母,再从分组数组里拿对应行的好友
        let sectionLetter = sortedSectionLetters[indexPath.section]
        guard let friendList = groupedFriends[sectionLetter] else { return UITableViewCell() }
        let currentFriend = friendList[indexPath.row]
        cell.configure(friend: currentFriend)
        return cell
    }
    // 每个分区的头部标题
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        String(sortedSectionLetters[section])
    }
    // 右侧快速索引条,效果和系统通讯录完全一致
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        sortedSectionLetters.map { String($0) }
    }
}

4. 修正页面跳转的取值逻辑

因为原来的平铺friendsArray已经不再作为表格的直接数据源,跳转传值的逻辑需要同步调整,避免出现选中行传错图片的问题:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "GoToDetailPhoto" {
        guard let destVC = segue.destination as? DetailPhotoCollectionViewController,
              let indexPath = tableView.indexPathForSelectedRow else { return }
        // 按新的分组结构取选中的好友
        let sectionLetter = sortedSectionLetters[indexPath.section]
        guard let friendList = groupedFriends[sectionLetter] else { return }
        let selectedFriend = friendList[indexPath.row]
        destVC.imageName = selectedFriend.photoImageName
    }
}

可优化的细节

  • 可以把数字、特殊符号开头的姓名统一归到#分组下,和系统通讯录的规则保持一致,避免出现零散的特殊符号分区
  • 如果需要兼容带重音的外文姓名(比如Élodie),可以在提取首字母前用folding方法把字符串转成无重音的普通字符,保证分组首字母正确
  • 如果想改回按全名首字母分组,只需要把提取姓氏的代码换成直接取name.first即可,整体分组逻辑不需要调整

内容的提问来源于stack exchange,提问作者user11345388

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 18:24:01