作者:gabriel theodoropoulos桶癣,原文鏈接,原文日期:2015-09-29
譯者:BridgeQ娘锁、星夜暮晨牙寞;校對(duì):小鐵匠Linus;定稿:
同每一代 iOS 系統(tǒng)版本的更新一樣莫秆,最新發(fā)布的 iOS 9 為用戶和開發(fā)者帶來(lái)了許多新特性以及原有功能的改善间雀。在這個(gè)版本中,我們不僅看到了很多首次推出的 API镊屎,還可以看到許多針對(duì)原有框架和類庫(kù)的更新惹挟。此外,一些舊版本的 API 被標(biāo)記為 deprecated
(校對(duì)注:意為新版本已被棄用)缝驳,而使用了更好的 API 來(lái)替代连锯。iOS 9 中,新的 Contacts framework (聯(lián)系人框架)是最好的例子了用狱,它是來(lái)代替原有 AddressBook framework 的运怖。該框架更加符合技術(shù)潮流且簡(jiǎn)單易用。
過(guò)去使用過(guò) AddressBook API 的開發(fā)者經(jīng)常會(huì)抱怨這個(gè)舊有的聯(lián)系人框架非常難用夏伊,大家普遍認(rèn)為它不易理解而且很難管理摇展,對(duì)開發(fā)者菜鳥來(lái)說(shuō)更是如此。然而溺忧,這些都已成為歷史咏连,全新的聯(lián)系人框架非常簡(jiǎn)單易用,通過(guò)它你可以很容易地查找砸狞、創(chuàng)建和更新聯(lián)系人信息捻勉,開發(fā)時(shí)間被極大地減少,擴(kuò)展更新也可以很快地實(shí)現(xiàn)刀森。
在接下來(lái)的部分中,我們將重點(diǎn)介紹 Contacts framework
中最主要的內(nèi)容报账。如果需要更多的技術(shù)細(xì)節(jié)研底,你可以去蘋果的官方文檔中查找埠偿,或者觀看 WWDC 2015 session 223 video 來(lái)學(xué)習(xí)。
首先榜晦,我們來(lái)談?wù)撘患浅V匾氖虑楣诮蔷褪怯脩綦[私。用戶總是在被詢問(wèn)是否允許應(yīng)用程序訪問(wèn)他們的聯(lián)系人數(shù)據(jù)乾胶,如果被允許抖剿,應(yīng)用就可以自由地同聯(lián)系人數(shù)據(jù)庫(kù)進(jìn)行交互,而如果用戶禁止訪問(wèn)识窿,那么應(yīng)用必須尊重用戶的選擇斩郎,即無(wú)法同聯(lián)系人數(shù)據(jù)進(jìn)行任何交互。稍后喻频,我們會(huì)談?wù)撚脩綦[私的更多細(xì)節(jié)缩宜,我們將看到如何通過(guò)程序的手段來(lái)處理所有可能的情況。此外甥温,要記住用戶總是有資格在手機(jī)設(shè)置的選項(xiàng)中更改應(yīng)用的授權(quán)狀態(tài)锻煌,所以在你想要執(zhí)行與聯(lián)系人數(shù)據(jù)相關(guān)的任務(wù)前,總應(yīng)該檢查你的應(yīng)用是否允許訪問(wèn)聯(lián)系人數(shù)據(jù)姻蚓。
聯(lián)系人數(shù)據(jù)的主要來(lái)源是設(shè)備內(nèi)置的數(shù)據(jù)庫(kù)宋梧。然而,新的聯(lián)系人框架不僅可以檢索這個(gè)數(shù)據(jù)庫(kù)狰挡,實(shí)際上捂龄,它還可以對(duì)別的來(lái)源進(jìn)行數(shù)據(jù)的檢索,比如通過(guò)你的 iCloud 賬戶(當(dāng)然是在你已經(jīng)連接了 iCloud 賬戶的情況下)圆兵,并且返回檢索到的聯(lián)系人結(jié)果跺讯。這是非常有用的,因?yàn)槟悴恍枰獑为?dú)再進(jìn)行某個(gè)來(lái)源的檢索殉农,你一次就能夠檢索所有數(shù)據(jù)刀脏,并且可以隨意管理轮傍。
新的聯(lián)系人框架包括了許多不同功能的類姑廉,所有類都非常重要倦西,但其中使用最多的一個(gè)是 CNContactStore
,它代表聯(lián)系人數(shù)據(jù)庫(kù)哗脖,并且提供了大量的操作方法茬暇,比如查詢、保存橱健、更新聯(lián)系人記錄而钞、授權(quán)檢查沙廉、授權(quán)請(qǐng)求等拘荡。 CNContact
表示一條聯(lián)系人記錄,并且它的內(nèi)部屬性都是不可變的撬陵,如果你想要?jiǎng)?chuàng)建或者更新一條已經(jīng)存在的聯(lián)系人記錄珊皿,你應(yīng)該使用它的可變版本 CNMutableContact
。值得注意的是巨税,當(dāng)你使用聯(lián)系人框架時(shí)蟋定,尤其是進(jìn)行聯(lián)系人查找時(shí),你應(yīng)該總是在后臺(tái)執(zhí)行草添。如果一條聯(lián)系人記錄的查找花費(fèi)較長(zhǎng)的時(shí)間并且在主線程執(zhí)行的話驶兜,你的應(yīng)用會(huì)無(wú)法響應(yīng),這會(huì)使應(yīng)用的用戶體驗(yàn)非常糟糕远寸。
當(dāng)導(dǎo)入聯(lián)系人數(shù)據(jù)到應(yīng)用中時(shí)抄淑,幾乎不需要導(dǎo)入所有的聯(lián)系人屬性。在所有聯(lián)系人框架允許的搜索范圍中檢索所有已存在的聯(lián)系人數(shù)據(jù)驰后,是一個(gè)非常費(fèi)資源的操作肆资,你應(yīng)該盡量避免這樣去做,除非你確定你真的需要使用所有的聯(lián)系人數(shù)據(jù)灶芝≈T可喜的是,新聯(lián)系人框架提供了僅檢索部分結(jié)果的方式夜涕,即檢索一個(gè)聯(lián)系人的部分屬性犯犁。比如說(shuō),你可以只查找聯(lián)系人的姓女器、名酸役、家庭郵件地址和家庭電話號(hào)碼,而撇開所有那些你不需要的數(shù)據(jù)晓避。
除了通過(guò)編程的方式來(lái)使用聯(lián)系人框架簇捍,它還提供了一些默認(rèn)的用戶界面(UI),可以讓你的應(yīng)用以直觀可視的方式訪問(wèn)聯(lián)系人數(shù)據(jù)俏拱。默認(rèn)提供的用戶界面跟手機(jī)自帶聯(lián)系人應(yīng)用幾乎一樣暑塑,也就是說(shuō)同樣有一個(gè)聯(lián)系人選擇控制器(contact picker view controller)用來(lái)選擇聯(lián)系人和聯(lián)系人屬性,一個(gè)聯(lián)系人視圖控制器用來(lái)展示聯(lián)系人的詳細(xì)信息并且處理某些操作(例如锅必,撥打電話)事格。
上面所有這些方面我們都將在本教程的后續(xù)部分詳細(xì)介紹惕艳。再次聲明,你可以通過(guò)官方文檔來(lái)學(xué)習(xí)所有這些方面的詳細(xì)內(nèi)容驹愚。接下來(lái)远搪,我們先來(lái)看一下示例程序是什么樣子,然后我們開始學(xué)習(xí)使用新的聯(lián)系人框架中的各種類逢捺,你會(huì)發(fā)現(xiàn)新的聯(lián)系人框架非常易用而且有趣谁鳍。
示例應(yīng)用簡(jiǎn)介
我試圖在本篇教程的示例應(yīng)用中,盡可能給大家全面地展示這個(gè)新框架的功能劫瞳。實(shí)際上倘潜,在以下部分我將會(huì)給大家展示:
- 檢查應(yīng)用是否準(zhǔn)許訪問(wèn)聯(lián)系人,并且如何請(qǐng)求授權(quán)志于。
- 使用三種不同的方式檢索聯(lián)系人涮因。其中一種方式將會(huì)涉及 Picker View Controller 的使用。
- 訪問(wèn)檢索到的聯(lián)系人屬性伺绽,并調(diào)整為適當(dāng)?shù)娘@示格式养泡。
- 使用默認(rèn)的 Contacts UI 來(lái)實(shí)現(xiàn)選擇、查看以及編輯聯(lián)系人奈应。
- 創(chuàng)建一個(gè)新的聯(lián)系人澜掩。
我將這個(gè)示例應(yīng)用命名為 Birthdays,因?yàn)槠淠康木褪钦故舅新?lián)系人生日信息钥组。同時(shí)输硝,還會(huì)顯示聯(lián)系人的全名、頭像(如果有的話)以及家庭 email 地址程梦。雖然在理想情況下点把,這個(gè)應(yīng)用的主要功能應(yīng)該是進(jìn)行生日提醒,不過(guò)我們并不會(huì)處理諸如通知屿附、發(fā)送短信之類的事情郎逃。
這個(gè)應(yīng)用是基于導(dǎo)航欄設(shè)計(jì)的,包含了以下幾個(gè)部分:
ViewController 是應(yīng)用啟動(dòng)時(shí)的默認(rèn)展示界面挺份。它將會(huì)展示我在上面所提及的所有信息褒翰,包括導(dǎo)入的聯(lián)系人,提供檢索聯(lián)系人的選項(xiàng)(右邊的導(dǎo)航欄按鈕)匀泊、創(chuàng)建新的聯(lián)系人(左邊的導(dǎo)航欄按鈕)以及通過(guò)單擊單元格來(lái)查看聯(lián)系人的具體信息:
聯(lián)系人詳情將會(huì)通過(guò)內(nèi)置的聯(lián)系人視圖控制器進(jìn)行展示优训。你會(huì)在后面看到,這個(gè)控制器既可以展示所有的聯(lián)系人信息各聘,也可以只顯示你感興趣的內(nèi)容揣非。
在接下來(lái)的內(nèi)容中,檢索聯(lián)系人將會(huì)是一個(gè)非常有意思的部分躲因。我會(huì)為大家展示三種進(jìn)行檢索的方法早敬,我將使用三種不同的思路:
- 第一種方法忌傻,我們將通過(guò)填寫聯(lián)系人姓名(或者姓名的一部分),點(diǎn)擊鍵盤上的返回按鈕搞监,然后應(yīng)用就會(huì)檢索所有匹配該姓名的聯(lián)系人水孩。
- 在下面這個(gè)截圖中您可以看到,屏幕中央有一個(gè)選擇器視圖琐驴。我們將會(huì)用它來(lái)尋找所有生日滿足對(duì)應(yīng)月份要求的聯(lián)系人俘种,月份可以在這個(gè)選擇器中進(jìn)行選擇,通過(guò)點(diǎn)擊右上角的 "Done" 導(dǎo)航欄按鈕棍矛,還會(huì)顯示檢索進(jìn)度安疗。
- 我們將使用框架所提供的默認(rèn)選擇器視圖控制器,來(lái)直接查看和檢索聯(lián)系人够委。值得注意的是,這個(gè)控制器可以自定義可用的聯(lián)系人怖现,此外其顯示風(fēng)格也可以自定義茁帽。大家會(huì)在后面部分看到如何操作。
這個(gè)就是選擇器視圖控制器屈嗤,其中只顯示了有生日記錄的聯(lián)系人:
我們這個(gè)應(yīng)用的最后一個(gè)部分就是創(chuàng)建新聯(lián)系人了潘拨。這個(gè)任務(wù)相當(dāng)簡(jiǎn)單,為了簡(jiǎn)單起見饶号,我們使用下面的這個(gè)視圖控制器來(lái)輸入我們要?jiǎng)?chuàng)建的聯(lián)系人姓名铁追、家庭 email 地址以及生日(我們不處理頭像,這玩意兒對(duì)于我們的示例來(lái)說(shuō)并不重要)茫船。
這個(gè)示例應(yīng)用所使用的數(shù)據(jù)(作為例子的聯(lián)系人信息)都是 iPhone 模擬器默認(rèn)數(shù)據(jù)庫(kù)中所包含的琅束。這些聯(lián)系人信息對(duì)我們來(lái)說(shuō)就已經(jīng)足夠了。當(dāng)然算谈,您也可以使用自己設(shè)備中的聯(lián)系人信息涩禀,或者給模擬器中添加新的聯(lián)系人。默認(rèn)情況下模擬器所提供的聯(lián)系人是沒(méi)有頭像的然眼,但是你可以從照片庫(kù)中簡(jiǎn)單地為聯(lián)系人添加頭像艾船。
一如往常,您可以下載這個(gè)起始項(xiàng)目高每,因?yàn)槲覀兘酉聛?lái)所做的工作將從它開始屿岂。一旦您下載完成,您可以打開這個(gè)項(xiàng)目然后瀏覽一下其中我添加的那些代碼鲸匿。當(dāng)您覺(jué)得準(zhǔn)備好的時(shí)候爷怀,就可以繼續(xù)閱讀下一個(gè)部分了。
Contact Store 類
我們?cè)谔幚砺?lián)系人的時(shí)候晒骇,經(jīng)常使用的一個(gè)基礎(chǔ)類就是 CNContactStore
類霉撵。這個(gè)類實(shí)際上代表了設(shè)備中所擁有的聯(lián)系人數(shù)據(jù)庫(kù)磺浙,它負(fù)責(zé)管理應(yīng)用和實(shí)際數(shù)據(jù)庫(kù)之間的數(shù)據(jù)交互操作。具體而言徒坡,它負(fù)責(zé)處理諸如檢索撕氧、保存、更新聯(lián)系人以及組記錄(group records)之類的工作喇完。簡(jiǎn)而言之伦泥,在使用聯(lián)系人信息的時(shí)候,這個(gè)類是絕大多數(shù)我們所能做的任務(wù)的起始點(diǎn)锦溪,并且我們將會(huì)在下面要寫的代碼中看到它不脯。
此外,我在概述中也提及了刻诊,用戶隱私是 iOS 中重要的組成部分防楷,因此在使用的時(shí)候千萬(wàn)要小心。眾所周知则涯,用戶可以準(zhǔn)許或者禁止第三方應(yīng)用訪問(wèn)他們的聯(lián)系人信息复局,因此確保您的應(yīng)用在任何時(shí)候都準(zhǔn)許顯示與任務(wù)有關(guān)的聯(lián)系人信息就變得至關(guān)重要。使用 CNContactStore
類粟判,您可以檢查您應(yīng)用當(dāng)前的認(rèn)證狀態(tài)亿昏,然后根據(jù)實(shí)際情況進(jìn)行相應(yīng)的處理。要記住档礁,每當(dāng)用戶在查看設(shè)置的時(shí)候角钩,都很有可能禁止應(yīng)用訪問(wèn)他們的聯(lián)系人信息,即使他們?cè)趹?yīng)用初次啟動(dòng)的時(shí)候同意了這個(gè)請(qǐng)求呻澜,因此在執(zhí)行任務(wù)前一定要確保您有權(quán)限執(zhí)行递礼,然后根據(jù)實(shí)際情況進(jìn)行相應(yīng)的處理。如果不這樣做的話易迹,往往會(huì)導(dǎo)致極差的用戶體驗(yàn)宰衙,這也是您應(yīng)當(dāng)極力避免的。在本教程的這部分里睹欲,我們會(huì)認(rèn)真考慮示例應(yīng)用的認(rèn)證狀態(tài)供炼。我們接下來(lái)將要做的,就是讓你能夠在項(xiàng)目中隨意使用它窘疮。
您將會(huì)發(fā)現(xiàn)袋哼,Contact Store 類很擅長(zhǎng)處理下面的情形(和其他方式相比):
- 檢索聯(lián)系人
- 創(chuàng)建(保存)新聯(lián)系人,以及更新聯(lián)系人信息
- 使用 Contact Picker 視圖控制器來(lái)選擇聯(lián)系人
要時(shí)刻記住闸衫,在整個(gè)類中我們只需要初始化一個(gè) CNContactStore
對(duì)象涛贯,并使用它即可。另一方面蔚出,雖然我們可以在需要的時(shí)候創(chuàng)建一個(gè)新的 CNContactStore
對(duì)象弟翘,但是由于這個(gè)類代表了代碼中的聯(lián)系人數(shù)據(jù)庫(kù)虫腋,那么為什么還要?jiǎng)?chuàng)建多個(gè)數(shù)據(jù)庫(kù)的實(shí)例呢?因此稀余,讓我們從打開 AppDelegate.swift
文件開始吧悦冀,聲明并初始化一個(gè) CNContactStore
屬性。在類的頂部添加以下代碼:
var contactStore = CNContactStore()
當(dāng)然睛琳,在類的頂部導(dǎo)入下面這個(gè)框架也是必要的:
import Contacts
好的盒蟆!現(xiàn)在,在我們處理應(yīng)用認(rèn)證狀態(tài)以及所有相關(guān)操作之前师骗,讓我們先寫兩個(gè)簡(jiǎn)便的輔助方法历等。注意這兩個(gè)方法并不是必須的,沒(méi)有它們我們?nèi)阅軌蚝芎玫毓ぷ鞅侔2贿^(guò)寒屯,實(shí)現(xiàn)這些有特定功能的方法將會(huì)帶來(lái)極大的便利。
因此愿待,第一個(gè)方法會(huì)讓其他類訪問(wèn)應(yīng)用委托 (AppDelegate) 變得更容易些浩螺。正常情況下,為了訪問(wèn)應(yīng)用委托我們需要使用下面這條語(yǔ)句:
UIApplication.sharedApplication().delegate as! AppDelegate
然而仍侥,我個(gè)人覺(jué)得,每次獲取應(yīng)用委托的時(shí)候都要寫上面這段代碼鸳君,實(shí)在是太煩人了农渊。我們?yōu)槭裁床粚懸粋€(gè)類方法呢?
class func getAppDelegate() -> AppDelegate {
return UIApplication.sharedApplication().delegate as! AppDelegate
}
通過(guò)這個(gè)方法或颊,我們可以以一個(gè)非常簡(jiǎn)單的方式來(lái)訪問(wèn)應(yīng)用委托中的所有屬性和方法砸紊。例如,我們可以從項(xiàng)目中的任意一個(gè)類中使用下面這行代碼訪問(wèn) contectStore
屬性囱挑。
AppDelegate.getAppDelegate().contactStore
第二個(gè)加在 AppDelegate.swift
文件中的輔助方法將會(huì)顯示一個(gè)帶有消息的警告控制器(alert controller)醉顽,我們每次使用它的時(shí)候只需要提供一個(gè)參數(shù)即可。實(shí)現(xiàn)起來(lái)并不復(fù)雜平挑,但是我們?cè)谶@里做了一點(diǎn)小小的特殊動(dòng)作游添;警告控制器必須通過(guò)視圖控制器來(lái)進(jìn)行顯示,然而應(yīng)用委托中并沒(méi)有視圖控制器的存在通熄。要解決這個(gè)問(wèn)題唆涝,我們首先必須要找到當(dāng)前顯示在應(yīng)用窗口上的頂層視圖控制器,然后在這個(gè)視圖控制器中顯示警告控制器唇辨。我們可以這么做:
func showMessage(message: String) {
let alertController = UIAlertController(title: "Birthdays", message: message, preferredStyle: UIAlertControllerStyle.Alert)
let dismissAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in
}
alertController.addAction(dismissAction)
let pushedViewControllers = (self.window?.rootViewController as! UINavigationController).viewControllers
let presentedViewController = pushedViewControllers[pushedViewControllers.count - 1]
presentedViewController.presentViewController(alertController, animated: true, completion: nil)
}
現(xiàn)在廊酣,我們要做的就是重點(diǎn)了,我們來(lái)處理應(yīng)用的認(rèn)證狀態(tài)赏枚。該狀態(tài)是通過(guò) CNAuthorizationStatus
枚舉來(lái)表示的亡驰,這個(gè)枚舉屬于 CNContactStore
類晓猛。它包含了下列四個(gè)枚舉值:
-
NotDetermined
:這個(gè)狀態(tài)說(shuō)明用戶暫未決定是否允許訪問(wèn)聯(lián)系人數(shù)據(jù)庫(kù)。當(dāng)應(yīng)用第一次安裝在設(shè)備上時(shí)將處于此狀態(tài)凡辱。 -
Restricted
:這個(gè)狀態(tài)說(shuō)明應(yīng)用不僅不能夠訪問(wèn)聯(lián)系人數(shù)據(jù)戒职,并且用戶也不能在設(shè)置中改變這個(gè)狀態(tài)。這個(gè)狀態(tài)是某些被激活的限制所導(dǎo)致的(比如說(shuō)家長(zhǎng)控制)煞茫。 -
Denied
:這個(gè)狀態(tài)說(shuō)明用戶不允許應(yīng)用訪問(wèn)聯(lián)系人數(shù)據(jù)帕涌。這個(gè)狀態(tài)只能夠被用戶改變。 -
Authorized
:這個(gè)狀態(tài)是所有應(yīng)用都希望擁有的续徽,這表明應(yīng)用能夠自由訪問(wèn)聯(lián)系人數(shù)據(jù)庫(kù)蚓曼,然后根據(jù)聯(lián)系人數(shù)據(jù)來(lái)處理某些任務(wù)。
有一點(diǎn)在這需要說(shuō)明清楚:應(yīng)用安裝之后钦扭,當(dāng)且僅當(dāng)用戶第一次嘗試執(zhí)行涉及聯(lián)系人數(shù)據(jù)(比如說(shuō)檢索聯(lián)系人)的操作時(shí)纫版,iOS 才會(huì)顯示一個(gè)預(yù)定義的警告控制器,詢問(wèn)用戶是否給應(yīng)用授權(quán):
如果用戶準(zhǔn)許授權(quán)客情,那么萬(wàn)事大吉其弊。然而,如果用戶禁止授權(quán)的話膀斋,那么應(yīng)用就不能夠獲取聯(lián)系人數(shù)據(jù)了梭伐,自然也沒(méi)法做任何操作了。在我們的示例應(yīng)用中仰担,對(duì)于這個(gè)特殊的情況糊识,我們會(huì)展示一個(gè)自定義的警告消息(使用我們此前定義的函數(shù)),告知用戶他必須在設(shè)置中準(zhǔn)許我們的應(yīng)用訪問(wèn)聯(lián)系人數(shù)據(jù)摔蓝。我們?cè)谝粋€(gè)新的函數(shù)中處理這個(gè)狀況赂苗,接下來(lái)我們會(huì)對(duì)其進(jìn)行實(shí)現(xiàn)。顯然贮尉,在這個(gè)函數(shù)中我們會(huì)盡可能考慮到所有的認(rèn)證狀態(tài)拌滋。我們先來(lái)看看函數(shù)吧,然后對(duì)其進(jìn)行簡(jiǎn)短的分析:
func requestForAccess(completionHandler: (accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts)
switch authorizationStatus {
case .Authorized:
completionHandler(accessGranted: true)
case .Denied, .NotDetermined:
self.contactStore.requestAccessForEntityType(CNEntityType.Contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(accessGranted: access)
}
else {
if authorizationStatus == CNAuthorizationStatus.Denied {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let message = "\(accessError!.localizedDescription)\n\nPlease allow the app to access your contacts through the Settings."
self.showMessage(message)
})
}
}
})
default:
completionHandler(accessGranted: false)
}
}
觀察上面這個(gè)函數(shù)猜谚,你會(huì)發(fā)現(xiàn)它包含了一個(gè) completionHandler 閉包败砂,當(dāng)應(yīng)用準(zhǔn)許訪問(wèn)聯(lián)系人的時(shí)候通過(guò)傳遞一個(gè) true 值來(lái)調(diào)用,不可訪問(wèn)的時(shí)候傳遞一個(gè) false 值龄毡。某些狀況非常簡(jiǎn)單吠卷,比如說(shuō) Authorized
或者 Restricted
,通過(guò) completionHandler 中傳遞的值可以很清楚的知道其操作沦零。然而祭隔,有趣的是,這里 Denied
和 NotDetermined
狀態(tài)的處理竟然是相同的,它們都會(huì)調(diào)用 requestAccessForEntityType:completionHandler
疾渴,因此應(yīng)用會(huì)請(qǐng)求授權(quán)千贯。我之前提到的自定義消息只會(huì)在 Denied
狀態(tài)下顯示。
值得注意的是搞坝, requestAccessForEntityType:completionHandler:
以及 authorizationStatusForEntityType:
這兩個(gè)方法都需要一個(gè) CNEntityType
參數(shù)搔谴。這是一個(gè)枚舉值,它其中只包含了一個(gè)名為 Contacts
的值桩撮。這個(gè)枚舉實(shí)際上指定了我們需要請(qǐng)求訪問(wèn)的實(shí)體敦第。
從下一節(jié)開始,上面這個(gè)函數(shù)將會(huì)被多次使用店量。每次我們執(zhí)行涉及到聯(lián)系人數(shù)據(jù)的操作時(shí)芜果,我們都會(huì)使用這個(gè)函數(shù),我們要確定聯(lián)系人數(shù)據(jù)是否準(zhǔn)許訪問(wèn)融师,當(dāng)然還要處理每個(gè)可能的情況右钾,以避免產(chǎn)生差的用戶體驗(yàn)。我們暫時(shí)沒(méi)有發(fā)現(xiàn)問(wèn)題旱爆,因?yàn)槲覀儨?zhǔn)備了一些可重用的代碼舀射,能夠讓我們接下來(lái)的工作更為便利。
使用斷言(Predicates)來(lái)檢索聯(lián)系人
正如我在概覽一節(jié)闡述過(guò)的怀伦,我們打算實(shí)現(xiàn)三種不同的方式來(lái)檢索聯(lián)系人數(shù)據(jù)脆烟。其中之一是通過(guò)在文本框中填寫我們想要檢索的聯(lián)系人全名或部分名字(無(wú)論是姓還是名),然后向聯(lián)系人框架請(qǐng)求結(jié)果房待。這就是我們即將開始的操作浩淘,實(shí)現(xiàn)此功能的核心函數(shù)是 unifiedContactsMatchingPredicate:keysToFetch:error:
。
這個(gè)函數(shù)作為 CNContactStore
類的一部分吴攒,接受兩個(gè)重要的參數(shù):
- Predicate:為了得到返回結(jié)果而用以檢索的
NSPredicate
對(duì)象。需要特別注意的是砂蔽,這里只接受從CNContact
類中得到的斷言洼怔,而不接受您自己創(chuàng)建的通用斷言(看這里)。在CNContact
類中所有支持的斷言函數(shù)中左驾,有一個(gè)名為predicateForContactsMatchingName:
的函數(shù)镣隶,我們將會(huì)使用它來(lái)生成斷言。 - keysToFetch:通過(guò)設(shè)定此參數(shù)诡右,您可以指定您想要檢索的部分聯(lián)系人數(shù)據(jù)安岂。這是一個(gè)描述需要檢索的聯(lián)系人(
CNContact
對(duì)象)屬性的字符串?dāng)?shù)組》牵框架提供了預(yù)定義的常量字符串值域那,可以用作關(guān)鍵詞來(lái)使用。
值得注意的是猜煮,這個(gè)方法可能會(huì)拋出異常次员,因此它必須要在 do-catch
聲明中使用 try
關(guān)鍵字來(lái)進(jìn)行修飾败许。然后在語(yǔ)句的 catch
模塊中對(duì)錯(cuò)誤情況進(jìn)行處理。
unifiedContactsMatchingPredicate:keysToFetch:error:
函數(shù)的結(jié)果包含了匹配給定斷言的 CNContact
對(duì)象的一個(gè)數(shù)組淑蔚,或者當(dāng)錯(cuò)誤發(fā)生的時(shí)候返回 nil市殷。
將上面的內(nèi)容牢記在心,現(xiàn)在就可以開始實(shí)現(xiàn)代碼了∩采溃現(xiàn)在打開 AddContactViewController.swift
文件醋寝,然后直接來(lái)到打開的類上方。在這里也要導(dǎo)入聯(lián)系人框架带迟,如果沒(méi)有它音羞,我們就沒(méi)法做事了:
import Contacts
我們現(xiàn)在前往 textFieldShouldReturn:
委托方法中。一開始我們會(huì)用上之前在應(yīng)用委托中創(chuàng)建的最后一個(gè)函數(shù)邮旷,并且檢查應(yīng)用是否有權(quán)限讀取聯(lián)系人黄选,以便繼續(xù):
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
}
}
return true
}
在準(zhǔn)許訪問(wèn)的情況下,為了匹配聯(lián)系人婶肩,我們要準(zhǔn)備好將進(jìn)行檢索的斷言和關(guān)鍵詞办陷。除此之外,我們還將聲明兩個(gè)變量:一個(gè)用于存儲(chǔ)結(jié)果的數(shù)組(如果有結(jié)果的話)律歼,以及如果沒(méi)有檢索到匹配聯(lián)系人或者檢索請(qǐng)求失敗的時(shí)候民镜,用以存儲(chǔ)自定義消息的字符串變量。
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsMatchingName(self.txtLastName.text!)
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey]
var contacts = [CNContact]()
var message: String!
}
}
return true
}
仔細(xì)觀察我們是如何聲明斷言和關(guān)鍵詞組的险毁,隨后我們繼續(xù)制圈。在下一步中,我們使用 try 關(guān)鍵字來(lái)檢索聯(lián)系人數(shù)據(jù)畔况,如果該操作成功的話鲸鹦,那么查詢結(jié)果就會(huì)寫入到我們此前初始化的 contacts 數(shù)組當(dāng)中。如果沒(méi)有找到聯(lián)系人或者檢索失敗的話跷跪,我們就會(huì)設(shè)定一個(gè)即將用來(lái)展示的自定義消息馋嗜;通過(guò)這幾個(gè)操作我們對(duì)這個(gè)函數(shù)的實(shí)現(xiàn)操作就即將完成了:
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsMatchingName(self.txtLastName.text!)
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
if message != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
AppDelegate.getAppDelegate().showMessage(message)
})
}
else {
}
}
}
return true
}
如你所見,我們現(xiàn)在遺留了一個(gè) else
語(yǔ)句暫未處理吵瞻,我們之后會(huì)回來(lái)補(bǔ)全這個(gè)遺漏的代碼的葛菇。這里最重要的是觀察我們是如何根據(jù)給定名字匹配聯(lián)系人數(shù)據(jù)的,并且是如何處理非預(yù)期狀況的橡羞。
展示檢索到的聯(lián)系人
最好的情況就是眯停,我們的檢索請(qǐng)求成功地返回了匹配到的聯(lián)系人信息,接著將他們顯示在 ViewController
類的表視圖(tableview)中卿泽,這就很有必要了莺债。然而,我們的第一步還是要讓 ViewController
類也得到檢索到的聯(lián)系人信息,因?yàn)槲覀兊乃袡z索操作都是在 AddContactViewController
中發(fā)生的九府。最好也是最簡(jiǎn)單的方法就是椎瘟,使用眾所周知的協(xié)議委托模式(Delegation pattern)。那么侄旬,讓我們朝著這個(gè)方向進(jìn)行吧肺蔚,繼續(xù)給我們的示例應(yīng)用添磚加瓦。
在 AddContactViewController.swift 文件的類上方儡羔,創(chuàng)建如下所示的協(xié)議宣羊,這個(gè)協(xié)議只有一個(gè)委托方法:
protocol AddContactViewControllerDelegate {
func didFetchContacts(contacts: [CNContact])
}
通過(guò)使用上面這個(gè)委托方法,我們不僅可以讓 ViewController 類知曉檢索到的聯(lián)系人信息汰蜘,還可以把它傳遞給新檢索到的聯(lián)系人仇冯。
接著,在 AddContactViewController
類中添加下面這個(gè)委托聲明:
var delegate: AddContactViewControllerDelegate!
還記得嗎族操,我們?cè)谏弦还?jié)中的 textFieldShouldReturn:
方法中遺留了一個(gè) else
沒(méi)有實(shí)現(xiàn)苛坚,現(xiàn)在是時(shí)候添加缺失的東西了。實(shí)際上色难,缺失的代碼只有兩行而已:第一行是調(diào)用上面聲明的委托方法泼舱,第二行則是通過(guò)導(dǎo)航控制器棧來(lái)推出視圖控制器。
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
...
if message != nil {
...
}
else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.didFetchContacts(contacts)
self.navigationController?.popViewControllerAnimated(true)
})
}
}
}
return true
}
如您所見枷莉,當(dāng)我們處理 UI 的時(shí)候一直都使用主線程。這是一個(gè)非常重要的細(xì)節(jié)笤妙,您應(yīng)當(dāng)牢記于心冒掌,否則的話 UI 就很有可能不會(huì)及時(shí)進(jìn)行更新,應(yīng)用也有可能出現(xiàn)一些無(wú)法預(yù)料的奇怪行為蹲盘。
這時(shí)候我們就可以前往 ViewController.swift 文件來(lái)處理檢索到的結(jié)果了股毫。一開始,我們也需要在這個(gè)類中導(dǎo)入 Contacts 框架:
import Contacts
接下來(lái)召衔,我們需要實(shí)現(xiàn)我們新的自定義協(xié)議皇拣,因此我們需要在類的頭部添加這個(gè)協(xié)議名:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddContactViewControllerDelegate
現(xiàn)在,是時(shí)候來(lái)聲明一個(gè) CNContact
對(duì)象的數(shù)組了薄嫡。這個(gè)數(shù)組將會(huì)存儲(chǔ)所有從檢索請(qǐng)求返回的聯(lián)系人數(shù)據(jù),它甚至還是表視圖的數(shù)據(jù)源丢早。因此救氯,在 ViewController
類的頂端添加以下代碼:
var contacts = [CNContact]()
除此之外扇商,我們還需要更新接下來(lái)將要進(jìn)行展示的表視圖的行數(shù):
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
在我們實(shí)現(xiàn)我們先前聲明的委托方法之前,我們需要讓 ViewController 類成為 AddContactViewControllerDelegate
協(xié)議的委托哑蔫。這會(huì)在 prepareForSegue:
函數(shù)中實(shí)現(xiàn):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "idSegueAddContact" {
let addContactViewController = segue.destinationViewController as! AddContactViewController
addContactViewController.delegate = self
}
}
}
最后,我們必須要實(shí)現(xiàn)我們自定義的委托方法。在委托方法中闸迷,我們將依次獲取所有返回的聯(lián)系人數(shù)據(jù)嵌纲,然后將它們添加到 contacts
數(shù)組中即可。當(dāng)然腥沽,我們會(huì)重新加載表視圖逮走,以便讓其顯示新的聯(lián)系人。
func didFetchContacts(contacts: [CNContact]) {
for contact in contacts {
self.contacts.append(contact)
}
tblContacts.reloadData()
}
現(xiàn)在讓我們來(lái)顯示這些聯(lián)系人信息吧今阳!對(duì)于每個(gè)單元格(cell)來(lái)說(shuō)师溅,我們都要顯示聯(lián)系人的姓和名,如果存在的話則還要顯示聯(lián)系人的生日盾舌、頭像以及家庭 email墓臭。具體的實(shí)現(xiàn)你會(huì)在下面的代碼中看到,我們將會(huì)修改很多東西妖谴,不過(guò)這足夠讓你理解聯(lián)系人屬性是如何被訪問(wèn)的了:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = "\(currentContact.givenName) \(currentContact.familyName)"
// 設(shè)置生日信息
if let birthday = currentContact.birthday {
cell.lblBirthday.text = "\(birthday.year)-\(birthday.month)-\(birthday.day)"
}
else {
cell.lblBirthday.text = "Not available birthday data"
}
// 設(shè)置聯(lián)系人頭像
if let imageData = currentContact.imageData {
cell.imgContactImage.image = UIImage(data: imageData)
}
// 設(shè)置聯(lián)系人的家庭 email 地址
var homeEmailAddress: String!
for emailAddress in currentContact.emailAddresses {
if emailAddress.label == CNLabelHome {
homeEmailAddress = emailAddress.value as! String
break
}
}
if homeEmailAddress != nil {
cell.lblEmail.text = homeEmailAddress
}
else {
cell.lblEmail.text = "Not available home email"
}
return cell
}
讓我們來(lái)通覽一遍上面的實(shí)現(xiàn)窿锉。首先,我們將姓和名連接起來(lái)膝舅,將其賦給了 “l(fā)blFullname” 標(biāo)簽嗡载。接下來(lái),我還會(huì)為你展示另一種實(shí)現(xiàn)方式铸史,不過(guò)現(xiàn)在我們就這么做鼻疮。接著,我們?cè)O(shè)置生日信息琳轿。如果生日數(shù)據(jù)存在的話判沟,我們就通過(guò)最簡(jiǎn)單的方式將其展示出來(lái)。注意到這只是一個(gè)臨時(shí)方法 (temporary approach)崭篡,之后我們會(huì)用正確的方式來(lái)處理這個(gè)出生日期挪哄。同樣,你必須知道生日數(shù)據(jù)并不是一個(gè) NSDate
對(duì)象琉闪,其實(shí)迹炼,它是一個(gè) NSDateComponents
對(duì)象,它可以轉(zhuǎn)換為 NSDate
后再轉(zhuǎn)換為 String
颠毙。
接下來(lái)我們要設(shè)置的是圖片數(shù)據(jù)斯入。如果不存在的話,你唯一能在這看到的就只是 imgContactImage
圖片視圖的背景顏色了蛀蜜,這個(gè)顏色是我在自定義的單元格 xib 文件中設(shè)定好的刻两。
最后,我們要設(shè)置的就是家庭 email 地址了滴某。你可以注意到的是磅摹,我們使用循環(huán)來(lái)遍歷了所有的 email 地址滋迈,直到我們找到所需要的那個(gè)為止。這是因?yàn)槁?lián)系人所擁有的 emailAddresses
屬性包含了被標(biāo)記為值 (CNLabeledValue) 對(duì)象所擁有的全部 email 地址户誓。最后饼灿,如果家庭 email 地址找到的話,我們就將其分別賦值給對(duì)應(yīng)的標(biāo)簽帝美,否則的話我們就將其設(shè)置為上面你所看到的消息碍彭。
如果你現(xiàn)在運(yùn)行這個(gè)應(yīng)用的話,輸入您想要選擇的聯(lián)系人名稱证舟,上面的實(shí)現(xiàn)或許可用硕旗,也可能不起作用。再次嘗試的話應(yīng)用會(huì)崩潰掉女责,但是你不必?fù)?dān)心漆枚。我們之后會(huì)修復(fù)這個(gè)問(wèn)題。我故意沒(méi)有給你上面方法的完整實(shí)現(xiàn)抵知,因?yàn)樯厦娴姆椒ǜ菀渍故緫?yīng)用是如何工作的墙基。
重新檢索聯(lián)系人
這個(gè)應(yīng)用可能會(huì)崩潰的原因在于,當(dāng)你請(qǐng)求聯(lián)系人數(shù)據(jù)的時(shí)候刷喜,它可能并沒(méi)有檢索到所有的值残制。為此,CNContact
類包含了一個(gè)名為 isKeyAvailable:
的方法掖疮,必須要在訪問(wèn)任何聯(lián)系人屬性之前使用初茶。比如說(shuō),在我們視圖顯示生日浊闪、頭像以及 email 地址之前恼布,我們應(yīng)該添加如下檢查:
if currentContact.isKeyAvailable(CNContactBirthdayKey) {
...
}
if currentContact.isKeyAvailable(CNContactImageDataKey) {
...
}
if currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
...
}
如果沒(méi)有對(duì)應(yīng)的關(guān)鍵詞的話,那么必須要采取合適的操作來(lái)重新檢索聯(lián)系人數(shù)據(jù)搁宾,然后嘗試再次顯示折汞。這就是我們?cè)谶@所要做的,明確來(lái)說(shuō)我們要在 ViewController 類中創(chuàng)建一個(gè)新的函數(shù)盖腿。然而爽待,在此之前,我們需要通過(guò)添加 isKeyAvailable:
方法來(lái)修復(fù)聯(lián)系人詳情的顯示問(wèn)題翩腐。實(shí)際上鸟款,我們創(chuàng)建一個(gè)條件來(lái)檢查所有的不可用關(guān)鍵詞即可,而不是為上面所提到的屬性使用三個(gè)不同的條件語(yǔ)句茂卦,并且如果有關(guān)鍵詞缺失的話欠雌,我們就調(diào)用下面將要實(shí)現(xiàn)的這個(gè)函數(shù),以便讓其重新檢索聯(lián)系人數(shù)據(jù)疙筹。我故意沒(méi)有包含進(jìn)聯(lián)系人名字的關(guān)鍵詞富俄,因此我們可以在下一個(gè)部分看到更多內(nèi)容。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = "\(currentContact.givenName) \(currentContact.familyName)"
if !currentContact.isKeyAvailable(CNContactBirthdayKey) || !currentContact.isKeyAvailable(CNContactImageDataKey) || !currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
refetchContact(contact: currentContact, atIndexPath: indexPath)
}
else {
// Set the birthday info.
if let birthday = currentContact.birthday {
cell.lblBirthday.text = "\(birthday.year)-\(birthday.month)-\(birthday.day)"
}
else {
cell.lblBirthday.text = "Not available birthday data"
}
// Set the contact image.
if let imageData = currentContact.imageData {
cell.imgContactImage.image = UIImage(data: imageData)
}
// Set the contact's work email address.
var homeEmailAddress: String!
for emailAddress in currentContact.emailAddresses {
if emailAddress.label == CNLabelHome {
homeEmailAddress = emailAddress.value as! String
break
}
}
if homeEmailAddress != nil {
cell.lblEmail.text = homeEmailAddress
}
else {
cell.lblEmail.text = "Not available home email"
}
}
return cell
}
上面調(diào)用的 refetchContact:atIndexPath:
函數(shù)是我們現(xiàn)在要實(shí)現(xiàn)的而咆。此外霍比,我覺(jué)得我們添加的那行條件語(yǔ)句非常明確,因此你能輕易理解其邏輯暴备。注意到做完這個(gè)改動(dòng)之后悠瞬,應(yīng)用就不再會(huì)發(fā)生崩潰了,即使返回的結(jié)果中出現(xiàn)了不可用的關(guān)鍵詞涯捻。
現(xiàn)在浅妆,讓我們看看這個(gè)新函數(shù)吧:
func refetchContact(contact contact: CNContact, atIndexPath indexPath: NSIndexPath) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
do {
let contactRefetched = try AppDelegate.getAppDelegate().contactStore.unifiedContactWithIdentifier(contact.identifier, keysToFetch: keys)
self.contacts[indexPath.row] = contactRefetched
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tblContacts.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
})
}
catch {
print("Unable to refetch the contact: \(contact)", separator: "", terminator: "\n")
}
}
}
}
首先,我們會(huì)檢查應(yīng)用是否有權(quán)限訪問(wèn)聯(lián)系人數(shù)據(jù)庫(kù)障癌。接著凌外,我們會(huì)指定想要檢索的特定結(jié)果關(guān)鍵詞,接著我們嘗試為給定的聯(lián)系人重新進(jìn)行數(shù)據(jù)檢索涛浙。注意到這個(gè)時(shí)候我們使用了一個(gè)新的方法來(lái)執(zhí)行檢索操作康辑,也就是 unifiedContactWithIdentifier:keysToFetch:
。這個(gè)方法的功能是重新檢索一個(gè)通過(guò)標(biāo)識(shí)符參數(shù)值所指定的聯(lián)系人數(shù)據(jù)轿亮。一旦結(jié)果得到返回疮薇,我們將會(huì)將位于 contacts 數(shù)組中的舊聯(lián)系人對(duì)象替換為新的。最后我注,我們就重新加載表視圖的特定行即可按咒。
這時(shí)候你可以自己重新運(yùn)行一遍應(yīng)用。重新檢索聯(lián)系人數(shù)據(jù)是一項(xiàng)您最好經(jīng)常執(zhí)行的任務(wù)但骨,以防止某些數(shù)據(jù)發(fā)生丟失励七,這樣你就可以確保應(yīng)用不會(huì)為用戶帶來(lái)出乎意料的“驚喜”。
輸出格式化
目前為止嗽冒,在單元格上顯示每個(gè)聯(lián)系人的生日信息之前呀伙,我們并沒(méi)有對(duì)其進(jìn)行正確的格式化操作。我們只是簡(jiǎn)單的連接并展示這些生日屬性而已添坊,但是現(xiàn)在我們已經(jīng)完成了前面的事情剿另,是時(shí)候來(lái)處理它了。
我們通過(guò)在 ViewController 類中創(chuàng)建新的自定義函數(shù)來(lái)解決這個(gè)問(wèn)題贬蛙。在其中雨女,我們會(huì)使用 NSDateFormatter
對(duì)象將日期轉(zhuǎn)換為一個(gè)本地化的字符串,但首先阳准,我們需要將日期組件 (date components氛堕,日期的每個(gè)部分) 轉(zhuǎn)換為 NSDate
對(duì)象。讓我們來(lái)看看這個(gè)新函數(shù):
func getDateStringFromComponents(dateComponents: NSDateComponents) -> String! {
if let date = NSCalendar.currentCalendar().dateFromComponents(dateComponents) {
let dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
let dateString = dateFormatter.stringFromDate(date)
return dateString
}
return nil
}
上面這個(gè)方法的參數(shù)是一個(gè)被 NSDateComponents
對(duì)象(在我們的例子中是出生日期)所表示的日期野蝇。返回值自然是一個(gè)字符串讼稚。為了將 dateComponents
對(duì)象轉(zhuǎn)換為 NSDate
對(duì)象括儒,只需要添加一行代碼即可。我們使用 NSCalendar
來(lái)進(jìn)行轉(zhuǎn)換锐想,以及使用將會(huì)初始化的日期格式化器 (date formatter) 對(duì)日期對(duì)象進(jìn)行處理帮寻。將這個(gè)日期格式化器的區(qū)域設(shè)置為當(dāng)前設(shè)備的區(qū)域,這是一個(gè)非常有必要的操作赠摇,只有這樣才能夠取得本地化的日期描述信息固逗。最后,我們要設(shè)置日期的樣式(不要太長(zhǎng)藕帜,也不要太短)烫罩,再執(zhí)行最后的轉(zhuǎn)換即可。最終洽故,轉(zhuǎn)換過(guò)的值將返回給調(diào)用者贝攒。
現(xiàn)在,讓我們來(lái)完善出生日期的顯示吧收津。其實(shí)饿这,只需要調(diào)用上面這個(gè)方法即可:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
if !currentContact.isKeyAvailable(CNContactBirthdayKey) || !currentContact.isKeyAvailable(CNContactImageDataKey) || !currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
refetchContact(contact: currentContact, atIndexPath: indexPath)
}
else {
// 設(shè)置生日信息
if let birthday = currentContact.birthday {
cell.lblBirthday.text = getDateStringFromComponents(birthday)
}
...
}
return cell
}
非常好,現(xiàn)在出生日期的顯示就更加高大上了撞秋。
現(xiàn)在讓我們來(lái)看看一些關(guān)于姓名顯示的有趣東西吧长捧。CNContact
類提供了一個(gè)內(nèi)置的格式化器,用以幫助我們輕松格式化兩類數(shù)據(jù):聯(lián)系人的全名 (CNContactFormatter) 以及地址 (CNPostalAddressFormatter)吻贿。這里我們將使用第一種串结,因此,聯(lián)系人的全名會(huì)被 Contacts 框架自動(dòng)格式化舅列。
首先肌割,我們先回到最后一次修改聯(lián)系人的方法,如下所示:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = CNContactFormatter.stringFromContact(currentContact, style: .FullName)
...
return cell
}
如你所見帐要,cell.lblFullname.text = “(currentContact.givenName) (currentContact.familyName)”
這行語(yǔ)句被下面這行替代了:
cell.lblFullname.text = CNContactFormatter.stringFromContact(currentContact, style: .FullName)
顯然把敞,我們不再需要將聯(lián)系人的姓與名連接起來(lái)而作為全名。CNContactFormatter
已經(jīng)替我們完成了這項(xiàng)工作榨惠,同時(shí)它還提供了一個(gè)本地化字符串(取決于設(shè)備的本地化設(shè)置奋早,通過(guò)合適的次序來(lái)設(shè)置名字部分)。
然而赠橙,上面這行代碼還是會(huì)導(dǎo)致一些問(wèn)題耽装,因?yàn)槁?lián)系人格式化器需要訪問(wèn)所有與聯(lián)系人名字相關(guān)聯(lián)的關(guān)鍵詞,即使這些關(guān)鍵詞我們并沒(méi)有在檢索的關(guān)鍵詞數(shù)組中期揪。不過(guò)掉奄,我們也沒(méi)有必要一個(gè)一個(gè)地將它們?nèi)繉懗鰜?lái)。所有相關(guān)的關(guān)鍵詞都可以通過(guò)關(guān)鍵詞描述符 (key descriptor) 所指定凤薛,這個(gè)描述符被用來(lái)替代關(guān)鍵詞數(shù)組中的單一關(guān)鍵詞姓建。
為了說(shuō)明得更具體一些诞仓,前往 AddContactViewController 文件的 textFieldShouldReturn:
方法。在那里速兔,將這行代碼:
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
替換為下面這行使用關(guān)鍵詞描述符的代碼:
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
正如上面所示狂芋,描述符格式化的方式是非常明確的。除此之外憨栽,其他的關(guān)鍵詞都保持不變。
上面的變化也必須在 refetchContact:
方法(在 ViewController
類中)進(jìn)行翼虫。你所需要做的就是將 keys
數(shù)組定義替換為上面的那行代碼屑柔,所以放手向前吧:
func refetchContact(contact contact: CNContact, atIndexPath indexPath: NSIndexPath) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
...
}
}
}
至此,我們已經(jīng)給代碼做完了所有與格式化相關(guān)的修改了珍剑。當(dāng)然掸宛,你仍然可以使用單個(gè)關(guān)鍵詞來(lái)檢索單個(gè)名字,不過(guò)這得取決于你的具體需求了招拙。
使用自定義過(guò)濾器檢索聯(lián)系人
我在此教程中提到的首要事情之一就是唧瘾,如何使用斷言來(lái)檢索聯(lián)系人。我們使用 Contacts 框架中的斷言來(lái)匹配給定名字的聯(lián)系人别凤,但是你是否記得饰序,通常情況下這個(gè)方法有一個(gè)缺點(diǎn)。我們必須使用框架內(nèi)置的斷言规哪,而我們無(wú)法對(duì)其進(jìn)行自定義求豫。那么問(wèn)題來(lái)了,我們?nèi)绾螌?shí)現(xiàn)自定義的過(guò)濾器來(lái)檢索聯(lián)系人呢诉稍?
對(duì)我們的示例應(yīng)用來(lái)說(shuō)蝠嘉,問(wèn)題可以變得更為具體一些,比如杯巨,如何才能基于聯(lián)系人的生日來(lái)檢索呢蚤告?在 AddContactViewController
類中有一個(gè)用于展示所有月份的選擇器視圖,因此現(xiàn)在我們所想做的是服爷,選擇一個(gè)月份杜恰,然后單擊“完成”按鈕,最后就可以獲得所有出生月份和所選月份相同的記錄了层扶。
好吧箫章,正如你所猜想的,的確是有一個(gè)辦法可以“應(yīng)用”自定義的過(guò)濾器镜会,但是會(huì)使整個(gè)過(guò)程比使用斷言還麻煩檬寂。通常情況下,我們所看到的方法是基于 CNContectStore
類中的 enumerateContactsWithFetchRequest(_:usingBlock) 方法戳表,這也是蘋果針對(duì)這種情況而建議使用的桶至。這個(gè)方法將會(huì)檢索所有的聯(lián)系人昼伴,因此自定義的查詢標(biāo)準(zhǔn) (criteria) 能夠在代碼塊 (閉包) 中設(shè)置,比如說(shuō)比較屬性值或者使用其他自定義的邏輯镣屹,并在最后獲得你所需要的聯(lián)系人信息圃郊。
在我們的例子中,我們將要檢查兩個(gè)東西:首先女蜈,我們必須要確保每個(gè)聯(lián)系人的生日都已被設(shè)定持舆,這樣可以避免任何可能出現(xiàn)的崩潰。其次伪窖,我們只要比較生日月份和在選擇器視圖中所選月份即可逸寓,如果有匹配的,就將這個(gè)聯(lián)系人放到數(shù)組當(dāng)中覆山。這個(gè)做法十分簡(jiǎn)單竹伸,因?yàn)樯帐?NSDateComponents
對(duì)象,因此我們能夠直接訪問(wèn)其月份簇宽。此外勋篓,剩下的操作也十分簡(jiǎn)單。我們將看到的所有操作已經(jīng)在之前的部分展示過(guò)了魏割,并且我也進(jìn)行了介紹譬嚣。接下來(lái),我們會(huì)在 AddContactViewController
類的 performDoneItemTap
自定義方法中寫下這些新代碼见妒,這樣就可以在視圖控制器中的“完成”按鈕被按下的時(shí)候就基于所選月份來(lái)檢索聯(lián)系人了孤荣。
代碼在此:
func performDoneItemTap() {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
var contacts = [CNContact]()
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
do {
let contactStore = AppDelegate.getAppDelegate().contactStore
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if contact.birthday != nil && contact.birthday!.month == self.currentlySelectedMonthIndex {
contacts.append(contact)
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.didFetchContacts(contacts)
self.navigationController?.popViewControllerAnimated(true)
})
}
catch let error as NSError {
print(error.description, separator: "", terminator: "\n")
}
}
}
}
如你所見,檢索完成后我們調(diào)用了委托须揣,這樣 ViewController
類中的表視圖就會(huì)根據(jù)新的聯(lián)系人數(shù)據(jù)進(jìn)行更新了盐股,接下來(lái)我們推出這個(gè)視圖控制器。上面這些代碼對(duì)你來(lái)說(shuō)在很多方面都十分有用耻卡,因?yàn)槟闼枰龅姆柚褪侵桓淖円幌挛挥谏鲜龃a塊中的過(guò)濾器規(guī)則條件即可。
聯(lián)系人選擇器視圖控制器(Contact Picker View Controller)
目前卵酪,我們所完成的所有聯(lián)系人管理操作都完全是基于代碼的幌蚊,然而我們的故事還沒(méi)有結(jié)束。Contacts 框架直接提供了視圖控制器 (UI)溃卡,可以以可視化的方式來(lái)訪問(wèn)聯(lián)系人溢豆,并立即與它們進(jìn)行交互。所提供的視圖控制器和“通訊錄”應(yīng)用中的控制器十分相像瘸羡,因此你可以借此得到用于選擇一個(gè)或多個(gè)聯(lián)系人的選擇器控制器漩仙,一個(gè)用于查看聯(lián)系人詳情的視圖控制器,以及一個(gè)可以編輯信息的表單。在選擇聯(lián)系人的時(shí)候队他,重寫默認(rèn)的控制器行為也是允許的卷仑,此外還有委托方法可以讓你處理結(jié)果。
在這一部分麸折,我們將設(shè)置這個(gè)選擇器視圖控制器锡凝,并在應(yīng)用的選擇器視圖控制器中選擇和導(dǎo)入聯(lián)系人。我們無(wú)需準(zhǔn)備太多其他的東西垢啼,不過(guò)定制程度將取決于每個(gè)應(yīng)用的需求窜锯。Contacts 框架允許設(shè)置三個(gè)可選的斷言,從而讓你自定義所顯示的聯(lián)系人信息:
-
predicateForEnablingContact
:這可能是你最常用的斷言了芭析。通過(guò)它衬浑,你可以指定在選擇器控制器中可用的聯(lián)系人。比如說(shuō)放刨,你可以通過(guò)它來(lái)完成聯(lián)系人的過(guò)濾,因此只有那些擁有可用生日的聯(lián)系人才能夠在選擇器中顯示出來(lái)尸饺。 -
predicateForSelectionOfContact
:通過(guò)它进统,你可以決定選擇器視圖控制器在被選擇的時(shí)候,應(yīng)該在何種情況下返回所選的聯(lián)系人浪听;以及何時(shí)應(yīng)該為顯示詳情視圖控制器而添額外的選擇螟碎。 -
predicateForSelectionOfProperty
:通過(guò)它,你可以指定某個(gè)屬性的默認(rèn)行為是否可以被執(zhí)行(比如說(shuō)當(dāng)點(diǎn)擊電話號(hào)碼時(shí)會(huì)執(zhí)行電話呼叫操作)迹栓,或者所按下的屬性是否應(yīng)該被返回掉分。
這里我們所打算使用的只是第一個(gè)斷言,打開選擇器視圖控制器克伊,只允許顯示有生日信息的聯(lián)系人信息酥郭。另外兩個(gè)斷言的使用也不難,但是我們這里暫時(shí)用不著它們愿吹;如果需要參考的話不从,我建議您分別查看斷言的文檔。
再次回到我們的應(yīng)用中犁跪,打開 AddContactViewController.swift 文件椿息。首先,到文件的頂端坷衍,導(dǎo)入 ContactsUI
框架寝优。
import ContactsUI
接著,實(shí)現(xiàn) CNContactPickerDelegate
協(xié)議枫耳,因此我們可以處理返回的聯(lián)系人:
class AddContactViewController: UIViewController, UITextFieldDelegate, UIPickerViewDelegate, CNContactPickerDelegate
從現(xiàn)在開始乏矾,我們的工作都將在 showContacts:
這個(gè) IBAction 方法中進(jìn)行。這個(gè)方法會(huì)啟用位于 AddContactViewController
底端的按鈕。讓我們來(lái)看看具體的實(shí)現(xiàn):
@IBAction func showContacts(sender: AnyObject) {
let contactPickerViewController = CNContactPickerViewController()
contactPickerViewController.predicateForEnablingContact = NSPredicate(format: "birthday != nil")
contactPickerViewController.delegate = self
presentViewController(contactPickerViewController, animated: true, completion: nil)
}
是不是非常簡(jiǎn)單妻熊!在這個(gè)示例應(yīng)用中我們不需要在單擊聯(lián)系人時(shí)顯示詳情頁(yè)面夸浅。不過(guò)如果需要的話,很容易使用這些屬性來(lái)展示詳情扔役。你所需要做的就是將一個(gè)包含所需關(guān)鍵詞的數(shù)組賦值給一個(gè)名為 displayedPropertyKeys
屬性帆喇。比如說(shuō),如果我們打算在應(yīng)用中展示詳情信息的話亿胸,我們就會(huì)在顯示選擇器視圖控制器之前增加一行代碼:
contactPickerViewController.displayedPropertyKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
幾分鐘前坯钦,我們實(shí)現(xiàn)了 CNContactPickerDelegate
協(xié)議,現(xiàn)在是時(shí)候來(lái)實(shí)現(xiàn)一個(gè)必須實(shí)現(xiàn)(required)的委托方法了侈玄。在方法中婉刀,我們會(huì)獲取所選擇聯(lián)系人,然后通過(guò)我們自己的代理方法將其發(fā)回給 ViewController
類當(dāng)中序仙。
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
delegate.didFetchContacts([contact])
navigationController?.popViewControllerAnimated(true)
}
假設(shè)你顯示了聯(lián)系人的詳情信息突颊,然后你想處理返回的屬性,你需要使用 contactPicker:didSelectContactProperty:
委托方法潘悼。我們?cè)谶@里不對(duì)其進(jìn)行實(shí)現(xiàn)律秃,因?yàn)槲覀儾恍枰D憧梢栽?a target="_blank" rel="nofollow">這里找到所有委托方法的集合治唤。
應(yīng)用現(xiàn)在可以繼續(xù)測(cè)試了棒动。這時(shí)候按下 “Open contacts to select” 按鈕來(lái)顯示選擇器視圖控制器。你會(huì)注意到?jīng)]有可用生日的聯(lián)系人是不會(huì)顯示出來(lái)的宾添。選擇其中一個(gè)聯(lián)系人船惨,然后你就會(huì)看到它出現(xiàn)在了 ViewController
的表視圖當(dāng)中。
聯(lián)系人視圖控制器
到目前為止缕陕,我們已經(jīng)實(shí)現(xiàn)了三種不同的方法以允許我們檢索聯(lián)系人并將其添加到應(yīng)用中來(lái)粱锐。然而,只在表視圖中顯示聯(lián)系人信息并不是一個(gè)很好的主意扛邑。我們想要更豐富的展現(xiàn)形式卜范,那就是在一個(gè)新的視圖控制器中顯示所選聯(lián)系人的詳情信息。實(shí)際上鹿榜,我們不需要?jiǎng)?chuàng)建一個(gè)自定義的控制器海雪,我們會(huì)使用由 Contacts 框架所提供的聯(lián)系人視圖控制器。通過(guò)它我們不僅能查看聯(lián)系人數(shù)據(jù)舱殿,還能夠?qū)ζ溥M(jìn)行編輯奥裸。當(dāng)然,通過(guò) CNContactViewController 類我們可以輕易獲得它沪袭。
讓我們回到 ViewController.swift 文件中來(lái)湾宙,然后處理一下用戶單擊聯(lián)系人時(shí)所發(fā)生的情況樟氢。然而,在我們顯示 CNContactViewController
實(shí)例之前侠鳄,我們需要確保所選聯(lián)系人的詳情信息中所有關(guān)鍵詞都可用埠啃。即便我們?cè)谡故久總€(gè)單元格的時(shí)候檢查了所有可用的關(guān)鍵詞,即便我們?cè)谛枰臅r(shí)候重新檢索了聯(lián)系人伟恶,但是當(dāng)用戶單擊此行單元格的速度比重新檢索操作的速度更快的時(shí)候碴开,一切就都不好說(shuō)了。因此博秫,我們必須要處理點(diǎn)東西潦牛。
之前,我們使用 CNContact
類中的 isKeyAvailable:
方法來(lái)檢查某個(gè)檢索到的聯(lián)系人中關(guān)鍵詞的可用性挡育。除了這個(gè)方法外巴碗,CNContact
還提供了另一種名為 areKeysAvailable:
的方法,我們可以用其來(lái)確保聯(lián)系人視圖控制器所需要的所有關(guān)鍵詞都已存在即寒。這個(gè)方法只接收一個(gè)參數(shù)橡淆,也就是一個(gè)包含關(guān)鍵詞或者關(guān)鍵詞描述符的數(shù)組 (和我們用來(lái)檢索聯(lián)系人時(shí)多次使用的關(guān)鍵詞數(shù)組類似)。就 CNContactViewController
而言母赵,雖然我們必須要設(shè)置 CNContactViewController.descriptorForRequiredKeys()
的特定值作為參數(shù)數(shù)組的唯一元素明垢。假設(shè)關(guān)鍵詞都可用的話,我們將會(huì)顯示聯(lián)系人視圖控制器市咽。如果不可用的話,我們就用之前的方法抵蚊,使用 descriptorForRequiredKeys()
來(lái)重新檢索聯(lián)系人施绎,從而指定所需要檢索的關(guān)鍵詞。
此外贞绳,我們?cè)谡麄€(gè)示例應(yīng)用中用來(lái)檢索聯(lián)系人數(shù)據(jù)的 keys
數(shù)組就會(huì)再次變得簡(jiǎn)單易用谷醉。不僅可以如我剛剛所述的那樣檢查可用性,還可以指定在聯(lián)系人視圖控制器中應(yīng)該顯示何種屬性冈闭。你可以在下面的實(shí)現(xiàn)中看到它是如何使用的俱尼。注意,要記住如果你省略了這個(gè)屬性萎攒,那么所有既有的聯(lián)系人屬性 (并不只是我們想顯示的) 都將在聯(lián)系人視圖控制器中顯示出來(lái)遇八。
上面說(shuō)了這么多,我們現(xiàn)在還是來(lái)看看這些代碼吧:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedContact = contacts[indexPath.row]
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
if selectedContact.areKeysAvailable([CNContactViewController.descriptorForRequiredKeys()]) {
let contactViewController = CNContactViewController(forContact: selectedContact)
contactViewController.contactStore = AppDelegate.getAppDelegate().contactStore
contactViewController.displayedPropertyKeys = keys
navigationController?.pushViewController(contactViewController, animated: true)
}
else {
AppDelegate.getAppDelegate().requestForAccess({ (accessGranted) -> Void in
if accessGranted {
do {
let contactRefetched = try AppDelegate.getAppDelegate().contactStore.unifiedContactWithIdentifier(selectedContact.identifier, keysToFetch: [CNContactViewController.descriptorForRequiredKeys()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let contactViewController = CNContactViewController(forContact: contactRefetched)
contactViewController.contactStore = AppDelegate.getAppDelegate().contactStore
contactViewController.displayedPropertyKeys = keys
self.navigationController?.pushViewController(contactViewController, animated: true)
})
}
catch {
print("Unable to refetch the selected contact.", separator: "", terminator: "\n")
}
}
})
}
}
在上面的代碼片段中耍休,你可以看到我們通過(guò)使用聯(lián)系人視圖控制器實(shí)例的 displayedPropertyKeys
屬性刃永,指定了我們想要展示的屬性。另一個(gè)值得提及的細(xì)節(jié)就是羊精,我們通過(guò) contactStore
屬性給聯(lián)系人視圖控制器提供了我們的聯(lián)系人存儲(chǔ)實(shí)例斯够。如果應(yīng)用中沒(méi)有 CNContactStore
實(shí)例的話這個(gè)設(shè)置就不是必要的,因?yàn)?CNContactsViewController
會(huì)自行創(chuàng)建一個(gè)新的存儲(chǔ)器。剩余的部分我們之前已經(jīng)討論過(guò)了读规。作為最后一步抓督,不要忘記在文件頭部導(dǎo)入下面這個(gè)框架:
import ContactsUI
新建并保存一個(gè)新聯(lián)系人
到目前為止,我們已經(jīng)見識(shí)了許多關(guān)于 Contacts 框架中的新東西了束亏。然而铃在,仍然有一個(gè)我們沒(méi)有討論的部分,那就是如何通過(guò)代碼創(chuàng)建一個(gè)新的聯(lián)系人并將其保存到數(shù)據(jù)庫(kù)中枪汪。因此涌穆,正如你所理解的,本教程的最后一個(gè)部分我們將要談?wù)撨@個(gè)話題雀久。我不會(huì)詳細(xì)說(shuō)明如何更新一個(gè)既有記錄宿稀,因?yàn)檫@個(gè)操作和我們?cè)谶@里將要看到的十分相似,因此我將這個(gè)操作完全留給你赖捌,你可以自己找一下這兩個(gè)操作之間的差異祝沸。
除了代表單個(gè)聯(lián)系人及其所有屬性的 CNContact
類之外,Contacts 框架還提供了一個(gè)名為 CNMutableContact
的類越庇。如它的名字所言罩锐,這個(gè)類和 CNContact
十分相似,它允許我們?yōu)槁?lián)系人的屬性賦予新值卤唉,因此就可以通過(guò)它來(lái)創(chuàng)建一個(gè)新的聯(lián)系人或者更新一個(gè)既有的聯(lián)系人涩惑。實(shí)際的保存 (以及更新) 操作將在我們所周知的聯(lián)系人存儲(chǔ)類 (CNContactStore
) 中處理,但是這是創(chuàng)建新聯(lián)系人的最后一步桑驱。你可以在下面看到額外的具體信息竭恬。
通常情況下,使用 CNMutableContact
類來(lái)設(shè)置某個(gè)聯(lián)系人的屬性值包含了一系列與獲取它們時(shí)完全相反的操作熬的。進(jìn)一步來(lái)說(shuō)痊硕,對(duì)于簡(jiǎn)單的屬性而言,直接分配一個(gè)單獨(dú)的值即可 (比如說(shuō)名)押框,特殊的屬性需要特殊對(duì)待岔绸。例如:
- 當(dāng)設(shè)置某個(gè)聯(lián)系人的出生日期的時(shí)候,必須創(chuàng)建一個(gè)
NSDateComponents
對(duì)象并將其賦給對(duì)應(yīng)的屬性 - 當(dāng)設(shè)置聯(lián)系人頭像的時(shí)候橡伞,必須要賦給一個(gè)
NSData
對(duì)象 - 當(dāng)設(shè)置 email 地址的時(shí)候盒揉,必須給每個(gè)單獨(dú)的 email 地址創(chuàng)建一個(gè)
CNLabeledValue
對(duì)象,然后所有的地址對(duì)象都應(yīng)該放到一個(gè)數(shù)組中賦值給emailAddresses
屬性兑徘。
上面的僅僅只是一些例子预烙。當(dāng)然還有很多聯(lián)系人屬性需要謹(jǐn)慎對(duì)待,不過(guò)無(wú)論如何道媚,接下來(lái)你會(huì)看到這些操作并不是很困難扁掸。
回到我們的示例應(yīng)用中來(lái)翘县,這時(shí)候我們要切換到 CreateContactViewController.swift 文件。在這個(gè)文件中谴分,你會(huì)找到一個(gè)空的名為 createContact()
的自定義函數(shù)锈麸,這是我們所有工作將要進(jìn)行的地方。簡(jiǎn)單而言牺蹄,我們將創(chuàng)建一個(gè) CNMutableContact
類的實(shí)例忘伞,然后設(shè)置我們感興趣的所有屬性值,最后我們將這個(gè)新紀(jì)錄存儲(chǔ)到數(shù)據(jù)庫(kù)中沙兰。讓我們來(lái)看一看實(shí)現(xiàn):
func createContact() {
let newContact = CNMutableContact()
newContact.givenName = txtFirstname.text!
newContact.familyName = txtLastname.text!
let homeEmail = CNLabeledValue(label: CNLabelHome, value: txtHomeEmail.text!)
newContact.emailAddresses = [homeEmail]
let birthdayComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day], fromDate: datePicker.date)
newContact.birthday = birthdayComponents
do {
let saveRequest = CNSaveRequest()
saveRequest.addContact(newContact, toContainerWithIdentifier: nil)
try AppDelegate.getAppDelegate().contactStore.executeSaveRequest(saveRequest)
navigationController?.popViewControllerAnimated(true)
}
catch {
AppDelegate.getAppDelegate().showMessage("Unable to save the new contact.")
}
}
我們從頭來(lái)看這些操作氓奈,第一步是初始化一個(gè) CNMutableContact
對(duì)象,這個(gè)對(duì)象將在后面一直使用鼎天。很明顯設(shè)置姓舀奶、名屬性是一個(gè)非常簡(jiǎn)單的操作。接下來(lái)的家庭 email 地址必須創(chuàng)建為一個(gè) CNLabeledValue
對(duì)象斋射,這也是上面代碼所展示的育勺。一旦新的 email 地址創(chuàng)建之后,就會(huì)作為 email 地址數(shù)組的一部分添加到 emailAddresses
屬性當(dāng)中罗岖。在我們的這個(gè)例子中断盛,我們沒(méi)有其他的地址扁誓。最后,我們基于用戶所挑選的日期卓箫,為這個(gè)新聯(lián)系人制定了出生日期淑倾。如上面的代碼所示蒂培,使用 NSCalendar
類并從 NSDate
對(duì)象創(chuàng)建一個(gè) NSDateComponents
對(duì)象是非常容易的搀缠。注意到日歷對(duì)象 (年筐眷、月、日) 是如何合并的垒手,借此它們產(chǎn)生了我們最終所想要的值。
這個(gè)代碼片段中最有趣的部分就是保存新聯(lián)系人的方式了倒信。你可以注意到科贬,首先是創(chuàng)建一個(gè) CNSaveRequest
對(duì)象,接著向其中添加新的聯(lián)系人對(duì)象鳖悠。到這里并沒(méi)有任何實(shí)際的存儲(chǔ)操作被執(zhí)行榜掌。這個(gè)操作而是發(fā)生在下一行代碼中,也就是調(diào)用聯(lián)系人存儲(chǔ)實(shí)例中的 executeSaveRequest:
方法的時(shí)候乘综。
假設(shè)新聯(lián)系人無(wú)法保存的話憎账,那么就會(huì)給用戶彈出一個(gè)帶有消息的警示框。
現(xiàn)在運(yùn)行這個(gè)應(yīng)用卡辰,使用 ViewController 左上角的按鈕來(lái)創(chuàng)建一個(gè)新的聯(lián)系人胞皱。保存你的記錄邪意,然后前去使用我們?cè)谥安糠謱?shí)現(xiàn)的檢索方法將其檢索出來(lái)。
重要提示:我在寫這篇教程的時(shí)候注意到一個(gè)問(wèn)題反砌,在我的測(cè)試中雾鬼,也就是創(chuàng)建一個(gè)新的記錄并將其保存到聯(lián)系人數(shù)據(jù)庫(kù)的時(shí)候,通過(guò)應(yīng)用訪問(wèn)聯(lián)系人詳情信息(通過(guò)點(diǎn)擊聯(lián)系人所在的行單元格)并不可用宴树。而且會(huì)在控制臺(tái)出現(xiàn)以下信息:
CNUI ERROR] error calling service – Couldn’t communicate with a helper application.
在網(wǎng)上我并不能找到任何可用的幫助策菜,我只好就此罷休,將其作為 BUG 報(bào)告給了蘋果酒贬。要牢牢記住又憨,在測(cè)試應(yīng)用的時(shí)候,要避免創(chuàng)建一個(gè)新的聯(lián)系人锭吨。
總結(jié)
在本教程的結(jié)尾蠢莺,我希望我已經(jīng)講清楚新的 Contacts 框架的易用性了。如果你過(guò)去曾經(jīng)使用過(guò) AddressBook API耐齐,那么你會(huì)發(fā)現(xiàn)在使用 Contacts 聯(lián)系人的時(shí)候一切都發(fā)生了巨大的變化浪秘。你可以盡情地把玩這個(gè)示例應(yīng)用,對(duì)其進(jìn)行修改埠况,以及按照你的意愿對(duì)其進(jìn)行擴(kuò)展耸携。這個(gè)應(yīng)用仍有提升的空間,但是千萬(wàn)不要忘記了用戶隱私協(xié)議辕翰,并且你必須要尊重用戶關(guān)于是否準(zhǔn)許應(yīng)用訪問(wèn)聯(lián)系人的選擇夺衍。不要錯(cuò)過(guò)了官方文檔,你會(huì)在那里發(fā)現(xiàn)更有意思的東西喜命。我希望你能夠享受本篇教程沟沙,并能發(fā)現(xiàn)其中有用的知識(shí)。下次我們?cè)僖姳陂牛M銚碛忻篮妹稀⒎e極的一天!
作為參考牌里,你可以在這里下載完整的 Xcode 項(xiàng)目颊咬。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)牡辽,最新文章請(qǐng)?jiān)L問(wèn) http://swift.gg喳篇。