iOS 9: Day by Day 第七天 Contacts框架

本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 7 :: Contacts Framework》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day7-contacts-framework)。感谢Chris Grant的辛苦工作!

iOS 9引入了一个新的联系人框架(Contact)。通过这个框架,我们可以使用Objective-C或者Swift API访问设备的通讯录。这比之前通过AddressBook框架进行访问改进了不少。AddressBook框架没有提供Objective-C接口,引起使用起来非常复杂。而对于Swift用户来说,这真是解决了一大痛点。

WWDC上宣布在iOS 9里废弃AddressBook后的欢呼声说明了开发者有多么痛恨这个框架。

使用该框架所获取的联系人都是唯一的,因此我们不需要担心从不同来源复制数据的时候,会产生重复内容。事实上,它们会被合并在一起再呈现给我们。因此并不需要我们手动的合并联系人信息。

使用新的联系人框架

下面我们将构建一个简单的应用程序来展示通讯录,并且允许查看联系人详情。

可以看到,我们使用的是Master-Detail应用程序,它同样可以在iPhone上运行。左侧显示通讯录,而右侧显示选中的联系人的图片、姓名、电话等详细信息。

获取用户的通讯录

使用Xcode的Master-Detail模板创建一个应用程序。打开MasterViewController,用import引入ContactsContactsUI框架。

import Contacts
import ContactsUI

替换默认的数据源行为,其中一个用于获取数据,另外一个用于显示当前联系人信息。

func findContacts() -> [CNContact] {
    let store = CNContactStore()
}

CNContactStore可以用于获取和保存联系人。虽然它可以被用来存取联系人组,但本文只会使用它来访问联系人信息。

let keysToFetch = [CNContactFormatter.descriptorForRequredKeysForStyle(.FullName), CNContactImageDataKey, CNContactPhoneNumbersKey]

let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)

一旦获取了存储对象(Store),就可以通过请求对象(Fetch Request)进行查询。创建CNContactFetchRequest需要通过数组说明请求的字段。其中有意思的一个是CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)。它是CNContactFormatter的一个快捷方法。如果没有这个方法的话,CNContactFormatter需要手动指定多个不同的Key:

[CNContactGivenNameKey,
 CNContactNamePrefixKey,
 CNContactNameSuffixKey,
 CNContactMiddleNameKey,
 CNContactFamilyNameKey,
 CNContactTypeKey...]

不但需要代码量较多,而且以后有可能发生变化。

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获取通讯录的代码非常简单。这个请求对象并没有明确过滤条件,因此会返回所有的联系人。

接下来就只要在MasterViewController中增加一个属性来保存所获取到的信息。

var contacts = [CNContact]()

然后在viewDidLoad中更新UITableView(异步)。

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建立IBOutlet连接。

@IBOutlet weak var contactImageView: UIImageView!
@IBOutlet weak var contactNameLabel: UILabel!
@IBOutlet weak var contactPhoneNumberLabel: UILabel!

一旦完成UI的设计,我们就可以给它们设置正确的值。下面是CNContact对象的使用方法。在configureView方法中添加以下代码:

label.text = CNContactFormatter.stringFromContact(contact, style: .FullName) 

根据上面的讨论,CNContactFormatter可以获取指定格式的联系人姓名字符串。在显示头像之前,需要检查是否设置了头像。imageData是Optional类型的变量,如果没有设置头像,可能会导致程序崩溃。

if contact.imageData != nil {
    imageView.image = UIImage(data: contact.imageData!)
}
else {
    imageView.image = nil
}

如果存在头像,使用它创建一个UIImage对象,并显示在UIImageView上。最后就是显示电话号码的代码:

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)
}

使用ContactsUI选取联系人

如果我们希望用户可以选取联系人并在程序中使用。上面的做法需要编写大量代码来手动获取信息和显示UI。实际上还有一种更简单的方式来做这件事情。iOS 9给我们提供了ContactsUI框架。它有一组可以显示联系人信息的视图控制器。

在这一节中,我们想要用户能够选取一个电话号码,并记录在程序中。由于这只是一个简单的示例,我们在MasterViewController的导航条右侧增加一个UIBarButtonItem作为入口。这个按钮关联的MasterViewController中方法如下:

@IBAction func showContactsPicker(sender: UIBarButtonItem) {
    let contactPicker = CNContactPickerViewController()
    contactPicker.delegate = self;
    contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey]

    self.presentViewController(contactPicker, animated: true, completion: nil)
}

我们创建了一个新的CNContactPickerViewController并设置代理。这样我们就可以获取用户响应,并指定只获取电话号码,然后显示视图控制器。

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对象以及感兴趣的属性。

点击MasterViewController右上角的UIBarButtonItem后,就会显示上图的界面。由于我们没有给CNContactPickerViewController添加过滤器,因此它会列出所有的联系人。

一旦选取一个联系人,就会得到他的所有电话号码,但是并不会显示其它信息。最后,如果选中其中一个属性,比如上图中的电话号码,就会调用contactPicker:didSelectContactProperty并回到上一个页面。

这样我们就拿到了对应联系人“Kate Bell”的CNContact对象以及“phoneNumber”,其中“5555648583”是电话号码。

总而言之,使用ContactsUI框架来选取联系人信息非常简单。我们我们需要更灵活的操作联系人,就需要使用Contacts框架的功能。

更多信息

请观看WWDC session 223:“Introducing the Contacts Framework for iOS and OS X”。别忘了我们的示例代码在GitHub上。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

  • 官网:戴维营教育http://www.diveinedu.com
  • 在线视频:戴维营学院http://v.diveinedu.com
  • 问答网:潜心俱乐部http://divein.club

你可能感兴趣的:(iOS 9: Day by Day 第七天 Contacts框架)