iOS 9 中,苹果介绍了新的 Contacts framework。允许用户使用 Objective-C 的 API 和设备的通讯录进行交互,同样适用于 Swift 语言。比起之前通过 AddressBook framework 来读取联系人信息来说,这是一个巨大的进步。因为 AddressBook framework 没有 Objective-C 的 API,非常难用,用 Swift 写的时候更是痛苦。希望新的 Contacts framework 能够解决这些痛点。
开发者有多不喜欢 AddressBook framework 呢?我想在 WWDC 的相关 session 里,当宣布 AddressBook framework 会在 iOS 9 中弃用后,现场爆发了最长时间、***声的欢呼,就是***的证明。
从 Framework 中返回的联系人是统一的,这意味着,如果你有从不同的数据源来的相同联系人数据,他们会自动合并,无需手动进行合并的操作。
使用新的 Contacts Framework
现在我们来创建一个简单的应用。这个应用展示一个你的通讯录的联系人列表,同时允许你查看(联系人的)详细信息。
contact result
如果你所见,这是一个 master detail view controller 应用,在 iPhone 同样可以很好的展示。在左边是一个你的设备上的联系人列表,右边可以看到联系人的头像、姓名、电话号码等详细信息。
获取用户的联系人
用Xcode 新建一个项目,只需要选择 master detail view controller 模版就可以开始了。他会给你设置好。
创建好项目后,打开 MasterViewController 类,首先我们要在头部引入 Contacts 和 ContactsUI 框架。
import Contacts
import ContactsUI
现在我们写一个方法,填充 datasrouce的特性。这个方法要读取和展示当前设备通讯录里的联系人。
func findContacts() -> [CNContact] {
let store = CNContactStore()
CNContactStore 是一个用来读取和保存联系人的新的类。这篇文章中我们仅仅展示如何读取联系人,但是你同样可以(用此方法)进行展示和保存联系人群组操作。
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
CNContactImageDataKey,
CNContactPhoneNumbersKey]
let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
当我们有了这个联系人数据库的引用后,我们需要创建一个指定条件的请求,通过这个 query 的请求去获取某些结果。创建一个 CNContactFetchRequest ,我们可以通过设置 contact keys 的数组,来获取我们需要的结果。有趣的是,我们可以通过CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName) 来格式化。这是CNContactFormattter 的一个非常方便的方法,稍后我们还会用到。
CNContactFormatter 需要很多不同的 keys,如果不使用 descriptorForRequiredKeysForStyle 方法,我们需要手动设置以下的 keys。
[CNContactGivenNameKey,
CNContactNamePrefixKey,
CNContactNameSuffixKey,
CNContactMiddleNameKey,
CNContactFamilyNameKey,
CNContactTypeKey...]
如你所见,要写一大堆代码。当 CNContactFormatter key 的需求发生改变,在从CNContactFormatter 生成一个字符串时,你会接到一个异常。
- var contacts = [CNContact]()
- do {
- try store.enumerateContactsWithFetchRequest(fetchRequest, usingBlock: { (let contact, let stop) -> Void in
- contacts.append(contact)
- })
- }
- catch let error as NSError {
- print(error.localizedDescription)
- }
- return contacts
这段代码非常简单。我们所做的是从 CNContactStore 中遍历所有符合我们需求的联系人。这个request 没有加任何的条件,所以会返回全部的联系人,包含我们需要的 keys。我们把每一条记录都逐个保存到一个数组中,返回。
现在我们要调用这个方法,用表格来展示结果。再次打开 MasterViewController, 添加一个属性,用来展示结果。
var contacts = [CNContact]()
更新 viewDidLoad 方法,用同步的方法调用并存储结果。
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
- self.contacts = self.findContacts()
- dispatch_async(dispatch_get_main_queue()) {
- self.tableView!.reloadData()
- }
- }
一旦保存好结果,刷新表格。
你需要修改一下 UITableViewDatasource 的方法来展示刚刚得到的结果。
- override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return self.contacts.count
- }
- override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
- let contact = contacts[indexPath.row] as CNContact
- cell.textLabel!.text = "\(contact.givenName) \(contact.familyName)"
- return cell
- }
现在剩下的就是在 DetailViewController 中展示联系人的详细信息了。这里我不在细述,你需要在 DetailViewController 中添加一个图像视图、两个标签视图,来展示头像、姓名和电话号码。并且在 interface builder 中创建 IBOutlet.
@IBOutlet weak var contactImageView: UIImageView!
@IBOutlet weak var contactNameLabel: UILabel!
@IBOutlet weak var contactPhoneNumberLabel: UILabel!
当这些做完,我们需要设置当前的值。在 configureView ,你需要添加下面这行代码。
label.text = CNContactFormatter.stringFromContact(contact, style: .FullName)
正如我们之前提到的,CNContactFormatter 能够很好的格式化联系人的名字。我们所要做的仅仅是按需求格式化他们,formatter可以很好的控制格式。
在设置头像时,我们需要先检测一下 imageData 是否存在。如果设备上的某个联系人没有设置头像, imageData 可能没有,(不检测的话)应用会崩溃。
- if contact.imageData != nil {
- imageView.image = UIImage(data: contact.imageData!)
- } else {
- imageView.image = nil
- }
如果存在,我们给 image view 设置好。
***,我们给电话号码标签指定值。
- if let phoneNumberLabel = self.contactPhoneNumberLabel {
- var numberArray = [String]()
- for number in contact.phoneNumbers {
- let phoneNumber = number.value as! CNPhoneNumber
- numberArray.append(phoneNumber.stringValue)
- }
- phoneNumberLabel.text = ", ".join(numberArray)
- }
这是最终的展示结果。现在,我们拥有一个app,可以在左侧,显示设备上通讯录中联系人的列表,并可以逐个找到他的详细信息。
contact details
使用 ContactsUI 选择联系人
也许我们希望这个应用,可以让用户自己选择联系人,并且展示详细信息给我们。正如此前你看到的,这可能要写很多代码。如果这些功能已经做好了的,会让开发变的更加简单。
这正是 ContactsUI framework 的功能。他提供了一套 view controllers,我们可以用在我们的应用中,展示联系人的信息。
在这一节,我们想让用户可以选择某个电话号码,并且保存起来。因为只是一个 demo,所以我们选择在 MasterViewController 的右上角添加一个 UIBarButtonItem,然后在 MasterViewController 类中,给 UIBarButtonItem 一个方法。
@IBAction func showContactsPicker(sender: UIBarButtonItem) {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self;
contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
self.presentViewController(contactPicker, animated: true, completion: nil)
}
我们创建了一个简单的 CNContactPickerViewController ,设置他的代理为 self.这样我们就能够响应他的请求,我们感兴趣的事电话号码,尽在选中电话号码后,展示联系人信息。CNContactPickerViewController 帮我们控制UI。
- func contactPicker(picker: CNContactPickerViewController, didSelectContactProperty contactProperty: CNContactProperty) {
- let contact = contactProperty.contact
- let phoneNumber = contactProperty.value as! CNPhoneNumber
- print(contact.givenName)
- print(phoneNumber.stringValue)
- }
在 contactPicker 代理方法 didSelectContactProperty 中,我们复制一个CNContactProperty 对象。这是 CNContact 的一个 wrapper。让我们来看一下他是怎么工作的。
contact picker
当我们点击 MasterViewController 右上角的 UIBarButtonItem 后,会展示一个页面。这个页面是所有联系人的列表,我们没有添加任何的过滤条件。
contact selected
当你点击某个联系人,会展示出这个联系人的电话列表。正是我们之前CNContactPhoneNumbersKey 里设置的一样,这个页面仅展示了我们需要的关键字段。
***,当你点击了页面中某些属性,例如电话号码后,会在 picker 关闭前触发 contactPicker:didSelectContactProperty方法。
在这个例子中,名字叫“Kate Bell”的联系人是 CNContact 的一个例子。“phoneNumbers”是 key,“5555648583”是 CNPhoneNumber 的值。*** identifier 字符串作为他的 identifier property.
总结一下,这个例子里我们使用 ContactsUI framework 来展示选取某个联系人,是多么简单和易用。如果你想开发更加丰富的页面,更自主的控制页面的展示信息,Contacts framework 会给你提供很好的获取数据信息的方式。
延伸阅读
更多关于 Contacts Framework 的信息,我推荐你观看WWDC 2015 的 session 223Introducing the Contacts Framework for iOS and OS X. ***不要忘了,你可以在Github 上找到我们已经创建的本篇文章的Demo项目。