如何按ContactsUI样式的字母分组获取联系人?
实现与ContactsUI一致的联系人分组效果
嘿,完全可以做到和系统ContactsUI一模一样的联系人分组!苹果提供了专门的API来处理本地化排序、分组逻辑,涵盖你提到的设备语言、变音符号、数字符号等所有场景,不用自己从零造轮子。下面是具体的实现步骤:
1. 先让联系人按系统规则排序
首先在你获取联系人的fetchContacts方法里,一定要设置CNContactFetchRequest的排序规则为用户默认值,这样会完全遵循系统通讯录的排序逻辑(比如姓在前还是名在前),同时自动处理变音符号和本地化排序:
func fetchContacts() { let store = CNContactStore() // 定义你需要获取的联系人字段 let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor] let fetchRequest = CNContactFetchRequest(keysToFetch: keys) // 关键:使用用户默认的排序规则,和系统通讯录保持一致 fetchRequest.sortOrder = .userDefault do { try store.enumerateContacts(with: fetchRequest) { contact, stop in self.favoritableContacts.append(FavoritableContact(contact: contact, hasFavorited: false)) } } catch { print("获取联系人失败:\(error.localizedDescription)") } }
2. 用UILocalizedIndexedCollation生成系统级分组
UILocalizedIndexedCollation是苹果专门用来处理本地化列表分组和索引的类,它会自动根据当前设备的语言、区域设置生成对应的分组标题和索引栏,完美匹配ContactsUI的表现:
实现步骤:
func organizeContactsIntoSections() { // 获取当前设备的本地化排序器 let collation = UILocalizedIndexedCollation.current() // 先把联系人按系统规则排序(用CNContactFormatter生成全名,保证和系统显示一致) let sortedContacts = favoritableContacts.sorted { contactA, contactB in guard let nameA = CNContactFormatter.string(from: contactA.contact, style: .fullName), let nameB = CNContactFormatter.string(from: contactB.contact, style: .fullName) else { return false } // 用collation的比较方法,确保排序逻辑和系统完全一致 return collation.compare(nameA, to: nameB) == .orderedAscending } // 初始化分组数组,数量和collation的分组标题数一致 var sections = Array(repeating: ExpandableNames(isExpanded: false, contacts: []), count: collation.sectionTitles.count) // 将每个联系人分配到对应的分组 for contact in sortedContacts { guard let fullName = CNContactFormatter.string(from: contact.contact, style: .fullName) else { continue } // 获取当前联系人对应的分组索引 let sectionIndex = collation.section(for: fullName, collationStringSelector: #selector(getter: NSString.description)) sections[sectionIndex].contacts.append(contact) } // 过滤掉空分组(避免出现没有联系人的分组) twoDimensionalArray = sections.filter { !$0.contacts.isEmpty } }
3. 自动处理特殊场景
这个方案会自动帮你搞定所有特殊情况:
- 数字/符号开头的联系人:会被统一分到
#分组,对应索引栏的#标识 - 变音符号:比如西班牙语的
ñ、法语的é,会根据当前语言的规则自动归类(西班牙语中ñ会单独成组,英语中会归到n组) - 非拉丁语言:中文、日文等语言会按照系统的拼音/假名排序规则生成对应的分组和索引栏,和系统通讯录完全一致
4. 配套TableView的索引栏
如果你的TableView需要显示右侧的索引栏,直接用collation.sectionIndexTitles即可:
override func sectionIndexTitles(for tableView: UITableView) -> [String]? { return UILocalizedIndexedCollation.current().sectionIndexTitles } override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { return UILocalizedIndexedCollation.current().section(forSectionIndexTitle: index) }
这样处理后,你的联系人列表不管是分组逻辑、排序顺序还是索引栏表现,都会和系统ContactsUI完全一致,用户体验拉满~
内容的提问来源于stack exchange,提问作者user8169082




