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 中返回的联系人是统一的,这意味着,如果你有从不同的数据源来的相同联系人数据,他们会自动合并,无需手动进行合并的操作。
现在我们来创建一个简单的应用。这个应用展示一个你的通讯录的联系人列表,同时允许你查看(联系人的)详细信息。
如果你所见,这是一个 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,可以在左侧,显示设备上通讯录中联系人的列表,并可以逐个找到他的详细信息。
也许我们希望这个应用,可以让用户自己选择联系人,并且展示详细信息给我们。正如此前你看到的,这可能要写很多代码。如果这些功能已经做好了的,会让开发变的更加简单。
这正是 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。让我们来看一下他是怎么工作的。
当我们点击 MasterViewController 右上角的 UIBarButtonItem 后,会展示一个页面。这个页面是所有联系人的列表,我们没有添加任何的过滤条件。
当你点击某个联系人,会展示出这个联系人的电话列表。正是我们之前CNContactPhoneNumbersKey 里设置的一样,这个页面仅展示了我们需要的关键字段。
最后,当你点击了页面中某些属性,例如电话号码后,会在 picker 关闭前触发 contactPicker:didSelectContactProperty方法。
在这个例子中,名字叫“Kate Bell”的联系人是 CNContact 的一个例子。“phoneNumbers”是 key,“5555648583”是 CNPhoneNumber 的值。最后 identifier 字符串作为他的 identifier property.
总结一下,这个例子里我们使用 ContactsUI framework 来展示选取某个联系人,是多么简单和易用。如果你想开发更加丰富的页面,更自主的控制页面的展示信息,Contacts framework 会给你提供很好的获取数据信息的方式。
更多关于 Contacts Framework 的信息,我推荐你观看WWDC 2015 的 session 223 Introducing the Contacts Framework for iOS and OS X . 最后不要忘了,你可以在 Github 上找到我们已经创建的本篇文章的 Demo 项目。