ios9學(xué)習(xí)系列: Contacts Framework
iOS 9 中,蘋(píng)果介紹了新的 Contacts framework。允許用戶(hù)使用 Objective-C 的 API 和設(shè)備的通訊錄進(jìn)行交互,同樣適用于 Swift 語(yǔ)言。比起之前通過(guò) AddressBook framework 來(lái)讀取聯(lián)系人信息來(lái)說(shuō),這是一個(gè)巨大的進(jìn)步。因?yàn)?AddressBook framework 沒(méi)有 Objective-C 的 API,非常難用,用 Swift 寫(xiě)的時(shí)候更是痛苦。希望新的 Contacts framework 能夠解決這些痛點(diǎn)。
開(kāi)發(fā)者有多不喜歡 AddressBook framework 呢?我想在 WWDC 的相關(guān) session 里,當(dāng)宣布 AddressBook framework 會(huì)在 iOS 9 中棄用后,現(xiàn)場(chǎng)爆發(fā)了最長(zhǎng)時(shí)間、***聲的歡呼,就是***的證明。
從 Framework 中返回的聯(lián)系人是統(tǒng)一的,這意味著,如果你有從不同的數(shù)據(jù)源來(lái)的相同聯(lián)系人數(shù)據(jù),他們會(huì)自動(dòng)合并,無(wú)需手動(dòng)進(jìn)行合并的操作。
使用新的 Contacts Framework
現(xiàn)在我們來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用。這個(gè)應(yīng)用展示一個(gè)你的通訊錄的聯(lián)系人列表,同時(shí)允許你查看(聯(lián)系人的)詳細(xì)信息。

contact result
如果你所見(jiàn),這是一個(gè) master detail view controller 應(yīng)用,在 iPhone 同樣可以很好的展示。在左邊是一個(gè)你的設(shè)備上的聯(lián)系人列表,右邊可以看到聯(lián)系人的頭像、姓名、電話(huà)號(hào)碼等詳細(xì)信息。
獲取用戶(hù)的聯(lián)系人
用Xcode 新建一個(gè)項(xiàng)目,只需要選擇 master detail view controller 模版就可以開(kāi)始了。他會(huì)給你設(shè)置好。
創(chuàng)建好項(xiàng)目后,打開(kāi) MasterViewController 類(lèi),首先我們要在頭部引入 Contacts 和 ContactsUI 框架。
import Contacts
import ContactsUI
現(xiàn)在我們寫(xiě)一個(gè)方法,填充 datasrouce的特性。這個(gè)方法要讀取和展示當(dāng)前設(shè)備通訊錄里的聯(lián)系人。
func findContacts() -> [CNContact] {
let store = CNContactStore()
CNContactStore 是一個(gè)用來(lái)讀取和保存聯(lián)系人的新的類(lèi)。這篇文章中我們僅僅展示如何讀取聯(lián)系人,但是你同樣可以(用此方法)進(jìn)行展示和保存聯(lián)系人群組操作。
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
CNContactImageDataKey,
CNContactPhoneNumbersKey]
let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
當(dāng)我們有了這個(gè)聯(lián)系人數(shù)據(jù)庫(kù)的引用后,我們需要?jiǎng)?chuàng)建一個(gè)指定條件的請(qǐng)求,通過(guò)這個(gè) query 的請(qǐng)求去獲取某些結(jié)果。創(chuàng)建一個(gè) CNContactFetchRequest ,我們可以通過(guò)設(shè)置 contact keys 的數(shù)組,來(lái)獲取我們需要的結(jié)果。有趣的是,我們可以通過(guò)CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName) 來(lái)格式化。這是CNContactFormattter 的一個(gè)非常方便的方法,稍后我們還會(huì)用到。
CNContactFormatter 需要很多不同的 keys,如果不使用 descriptorForRequiredKeysForStyle 方法,我們需要手動(dòng)設(shè)置以下的 keys。
[CNContactGivenNameKey,
CNContactNamePrefixKey,
CNContactNameSuffixKey,
CNContactMiddleNameKey,
CNContactFamilyNameKey,
CNContactTypeKey...]
如你所見(jiàn),要寫(xiě)一大堆代碼。當(dāng) CNContactFormatter key 的需求發(fā)生改變,在從CNContactFormatter 生成一個(gè)字符串時(shí),你會(huì)接到一個(gè)異常。
- 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
 
這段代碼非常簡(jiǎn)單。我們所做的是從 CNContactStore 中遍歷所有符合我們需求的聯(lián)系人。這個(gè)request 沒(méi)有加任何的條件,所以會(huì)返回全部的聯(lián)系人,包含我們需要的 keys。我們把每一條記錄都逐個(gè)保存到一個(gè)數(shù)組中,返回。
現(xiàn)在我們要調(diào)用這個(gè)方法,用表格來(lái)展示結(jié)果。再次打開(kāi) MasterViewController, 添加一個(gè)屬性,用來(lái)展示結(jié)果。
var contacts = [CNContact]()
更新 viewDidLoad 方法,用同步的方法調(diào)用并存儲(chǔ)結(jié)果。
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
 - self.contacts = self.findContacts()
 - dispatch_async(dispatch_get_main_queue()) {
 - self.tableView!.reloadData()
 - }
 - }
 
一旦保存好結(jié)果,刷新表格。
你需要修改一下 UITableViewDatasource 的方法來(lái)展示剛剛得到的結(jié)果。
- 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
 - }
 
現(xiàn)在剩下的就是在 DetailViewController 中展示聯(lián)系人的詳細(xì)信息了。這里我不在細(xì)述,你需要在 DetailViewController 中添加一個(gè)圖像視圖、兩個(gè)標(biāo)簽視圖,來(lái)展示頭像、姓名和電話(huà)號(hào)碼。并且在 interface builder 中創(chuàng)建 IBOutlet.
@IBOutlet weak var contactImageView: UIImageView!
@IBOutlet weak var contactNameLabel: UILabel!
@IBOutlet weak var contactPhoneNumberLabel: UILabel!
當(dāng)這些做完,我們需要設(shè)置當(dāng)前的值。在 configureView ,你需要添加下面這行代碼。
label.text = CNContactFormatter.stringFromContact(contact, style: .FullName)
正如我們之前提到的,CNContactFormatter 能夠很好的格式化聯(lián)系人的名字。我們所要做的僅僅是按需求格式化他們,formatter可以很好的控制格式。
在設(shè)置頭像時(shí),我們需要先檢測(cè)一下 imageData 是否存在。如果設(shè)備上的某個(gè)聯(lián)系人沒(méi)有設(shè)置頭像, imageData 可能沒(méi)有,(不檢測(cè)的話(huà))應(yīng)用會(huì)崩潰。
- if contact.imageData != nil {
 - imageView.image = UIImage(data: contact.imageData!)
 - } else {
 - imageView.image = nil
 - }
 
如果存在,我們給 image view 設(shè)置好。
***,我們給電話(huà)號(hào)碼標(biāo)簽指定值。
- 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)
 - }
 
這是最終的展示結(jié)果。現(xiàn)在,我們擁有一個(gè)app,可以在左側(cè),顯示設(shè)備上通訊錄中聯(lián)系人的列表,并可以逐個(gè)找到他的詳細(xì)信息。
 
contact details
使用 ContactsUI 選擇聯(lián)系人
也許我們希望這個(gè)應(yīng)用,可以讓用戶(hù)自己選擇聯(lián)系人,并且展示詳細(xì)信息給我們。正如此前你看到的,這可能要寫(xiě)很多代碼。如果這些功能已經(jīng)做好了的,會(huì)讓開(kāi)發(fā)變的更加簡(jiǎn)單。
這正是 ContactsUI framework 的功能。他提供了一套 view controllers,我們可以用在我們的應(yīng)用中,展示聯(lián)系人的信息。
在這一節(jié),我們想讓用戶(hù)可以選擇某個(gè)電話(huà)號(hào)碼,并且保存起來(lái)。因?yàn)橹皇且粋€(gè) demo,所以我們選擇在 MasterViewController 的右上角添加一個(gè) UIBarButtonItem,然后在 MasterViewController 類(lèi)中,給 UIBarButtonItem 一個(gè)方法。
@IBAction func showContactsPicker(sender: UIBarButtonItem) {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self;
contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
self.presentViewController(contactPicker, animated: true, completion: nil)
}
我們創(chuàng)建了一個(gè)簡(jiǎn)單的 CNContactPickerViewController ,設(shè)置他的代理為 self.這樣我們就能夠響應(yīng)他的請(qǐng)求,我們感興趣的事電話(huà)號(hào)碼,盡在選中電話(huà)號(hào)碼后,展示聯(lián)系人信息。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 中,我們復(fù)制一個(gè)CNContactProperty 對(duì)象。這是 CNContact 的一個(gè) wrapper。讓我們來(lái)看一下他是怎么工作的。
 
contact picker
當(dāng)我們點(diǎn)擊 MasterViewController 右上角的 UIBarButtonItem 后,會(huì)展示一個(gè)頁(yè)面。這個(gè)頁(yè)面是所有聯(lián)系人的列表,我們沒(méi)有添加任何的過(guò)濾條件。
 
contact selected
當(dāng)你點(diǎn)擊某個(gè)聯(lián)系人,會(huì)展示出這個(gè)聯(lián)系人的電話(huà)列表。正是我們之前CNContactPhoneNumbersKey 里設(shè)置的一樣,這個(gè)頁(yè)面僅展示了我們需要的關(guān)鍵字段。
***,當(dāng)你點(diǎn)擊了頁(yè)面中某些屬性,例如電話(huà)號(hào)碼后,會(huì)在 picker 關(guān)閉前觸發(fā) contactPicker:didSelectContactProperty方法。
在這個(gè)例子中,名字叫“Kate Bell”的聯(lián)系人是 CNContact 的一個(gè)例子。“phoneNumbers”是 key,“5555648583”是 CNPhoneNumber 的值。*** identifier 字符串作為他的 identifier property.
總結(jié)一下,這個(gè)例子里我們使用 ContactsUI framework 來(lái)展示選取某個(gè)聯(lián)系人,是多么簡(jiǎn)單和易用。如果你想開(kāi)發(fā)更加豐富的頁(yè)面,更自主的控制頁(yè)面的展示信息,Contacts framework 會(huì)給你提供很好的獲取數(shù)據(jù)信息的方式。
延伸閱讀
更多關(guān)于 Contacts Framework 的信息,我推薦你觀(guān)看WWDC 2015 的 session 223Introducing the Contacts Framework for iOS and OS X. ***不要忘了,你可以在Github 上找到我們已經(jīng)創(chuàng)建的本篇文章的Demo項(xiàng)目。















 
 
 





 
 
 
 