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项目。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容