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
相关产品推荐
相关产品推荐

