iOS9 Day-by-Day :: Day 7 :: Contacts Framework

原文地址

这是一个系列文章,查看更多请移步目录页

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 223 Introducing the Contacts Framework for iOS and OS X. 最后不要忘了,你可以在 Github 上找到我们已经创建的本篇文章的Demo项目。

这是一个系列文章,查看更多请移步目录页

推荐阅读更多精彩内容