iOS 9:联系人框架简介

介绍

通过iOS 9,OS X El Capitan和watchOS 2,Apple引入了全新的框架Contacts 该框架提供了一种面向对象的方法来处理用户的联系信息,并取代了基于功能的通讯簿框架。

在本教程中,我们将在iOS上重新实现Contacts应用程序的基本功能,以便您可以了解这些新API的工作方式。

先决条件

本教程要求您在OS X Yosemite或更高版本上运行Xcode 7+。 您还需要从GitHub下载入门项目。

1.访问联系人

我们首先要使用Contacts框架来访问用户的联系人并将其显示在表格视图中。 打开入门项目,然后转到MasterViewController.swift

如果滚动到文件顶部,可以看到我已经为Contact框架添加了import语句。 这使我们可以访问框架中定义的类,协议和常量。

import Contacts

MasterViewController类中,将getContacts()方法的空实现替换为以下内容。 确保您还添加了如下所示的retrieveContactsWithStore(_:)方法。

func getContacts() {
    let store = CNContactStore()
    
    if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
        store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
            if authorized {
                self.retrieveContactsWithStore(store)
            }
        })
    } else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
        self.retrieveContactsWithStore(store)
    }
}
func retrieveContactsWithStore(store: CNContactStore) {
    do {
        let groups = try store.groupsMatchingPredicate(nil)
        let predicate = CNContact.predicateForContactsInGroupWithIdentifier(groups[0].identifier)
        //let predicate = CNContact.predicateForContactsMatchingName("John")
        let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey]
        
        let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)
        self.objects = contacts
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.tableView.reloadData()
        })
    } catch {
        print(error)
    }
}

让我们逐步看一下这段代码。 我们创建一个CNContactStore实例,该对象用于直接与iOS上的Contacts系统通信。 然后,我们检查CNContactStore的授权状态。 如果不确定,我们请求授权,如果成功,则检索联系人。 如果该应用程序已被授权,我们将立即获取用户的联系人。

retrieveContactsWithStore(_:)方法中,我们将代码包装在do-catch语句中,因为我们使用的两个方法是throwing方法。 您可以在Envato Tuts +上阅读有关投掷方法和错误处理的更多信息。

do子句中,我们获取设备上的联系人组。 使用CNContact类,我们创建一个NSPredicate对象,该对象与我们刚检索的第一个组中的所有联系人匹配。

然后,我们创建一个包含多个常量键的数组。 这些键也直接与您的应用有权访问的信息有关。 对于您未指定的任何键(例如,电话号码),您的应用将无法访问该信息。 使用“联系人”框架时,这称为部分联系人,因为您无权访问所有联系人信息。

使用store对象,我们检索与我们先前创建的谓词相匹配并具有指定键的联系人。 我们将结果分配给视图控制器的objects数组,以在表视图中显示。 我们强制将表视图重新加载到主线程上。 这很重要,因为获取联系人是在后台线程上执行的。

需要注意一些关键事项:

  • CNContactFormatter上使用的descriptorForRequiredKeysForStyle(_:)类方法是一种方便的方法,可以轻松地添加查看联系人姓名所需的所有键。
  • 您创建的谓词不一定要用于联系人组。 它可以是许多事情之一,例如,搜索匹配的名称。
let predicate = CNContact.predicateForContactsMatchingName("John")
  • 在“联系人”框架中,您的应用无法直接访问每个联系人。 这就是为什么我们使用上面的代码来检索第一个联系人组而不是所有联系人的原因。
  • 检索联系人时,我们使用unifiedContactsMatchingPredicate(_:keysToFetch:)方法。 在这种情况下,“统一”是什么意思? 如果用户有多个与同一个人相关的联系人,则可以在“联系人”应用程序中将它们链接在一起。 当您的应用尝试访问此用户的联系人时,而不是返回多个CNContact实例,Contacts框架这些实例统一为一个对象,因此您的应用可以正确显示信息并轻松地对其进行解释。

接下来,我们需要在表格视图中显示联系人的信息。 MasterViewController类中,将tableView(_:cellForRowAtIndexPath:)的实现替换为以下内容:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    
    let contact = self.objects[indexPath.row]
    let formatter = CNContactFormatter()
    
    cell.textLabel?.text = formatter.stringFromContact(contact)
    cell.detailTextLabel?.text = contact.emailAddresses.first?.value as? String
    
    return cell
}

我们利用CNContactFormatter类轻松获取联系人姓名的String值。 我们还将获取联系人的第一个电子邮件地址(由CNLabeledValue对象表示)并获取其值。 CNLabeledValue对象表示可能需要上下文信息的任何联系信息。 这些对象仅包含一个标签和一个值。 在下面的示例中, 粗体字表示项目的标签, 斜体字表示其值。

  • 住家 地址
  • 工作 电话
  • 个人 电邮

在模拟器中构建并运行您的应用。 首次运行该应用程序时,应该看到以下警报。

iOS 9:联系人框架简介_第1张图片

单击“ 确定”后 ,表格视图应显示一项,如下所示。

iOS 9:联系人框架简介_第2张图片

从表格视图中选择联系人后,现在该填写详细信息视图了。 检索联系人时,我们仅获取了足够的键来访问联系人的姓名和电子邮件地址。 对于我们的应用程序的详细视图,我们还希望显示其地址以及个人资料图片。 为此,我们可以在检索联系人时在MasterViewController类中添加额外的键。 但是,我们将使用联系人的标识符通过所需的键再次检索同一联系人。

打开DetailViewController.swift并用以下代码替换configureView()的实现。

func configureView() {
    // Update the user interface for the detail item.
    if let oldContact = self.contactItem {
        let store = CNContactStore()
        
        do {
            let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
            let contact = try store.unifiedContactWithIdentifier(oldContact.identifier, keysToFetch: keysToFetch)
            
            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                if contact.imageDataAvailable {
                    if let data = contact.imageData {
                        self.contactImage.image = UIImage(data: data)
                    }
                }
                
                self.fullName.text = CNContactFormatter().stringFromContact(contact)
                
                self.email.text = contact.emailAddresses.first?.value as? String
                
                if contact.isKeyAvailable(CNContactPostalAddressesKey) {
                    if let postalAddress = contact.postalAddresses.first?.value as? CNPostalAddress {
                        self.address.text = CNPostalAddressFormatter().stringFromPostalAddress(postalAddress)
                    } else {
                        self.address.text = "No Address"
                    }
                }
            })
        } catch {
            print(error)
        }
    }
}

让我们分解一下实现。 我们获得了从MasterViewController收到的联系人项的未包装引用,并创建了另一个CNContactStore实例。

do-catch语句中,我们创建另一个keysToFetch数组,这一次具有用于邮政地址,图像数据和图像数据的键。 然后,我们使用联系人存储通过使用unifiedContactWithIdentifier(_:keysToFetch:)方法来检索具有所需信息的新CNContact实例。

同样,请注意,我们在主线程上更新了用户界面。 我们检查联系人是否有可加载的图像数据,并在可能的情况下将其转换为UIImage对象。 我们用正确的信息填充fullNameemail标签。

尽管在此示例中并不是绝对必要的,因为我们知道哪些键可用,但我们检查以查看我们的应用程序是否可以访问联系人的邮政地址信息。 请注意,此步骤仅是示例,但是如果不确定可以访问哪些信息,则应始终与联系人联系。

我们检索联系人的第一个邮政地址(由CNPostalAddress类表示),然后使用CNPostalAddressFormatter实例将其格式化为字符串。 CNPostalAddress类的功能类似于CNContact类,但是它具有不同的属性,例如街道,省和国家。

在模拟器中构建并运行您的应用,然后从列表中选择一个联系人。 出现的详细信息视图应如下所示:

iOS 9:联系人框架简介_第3张图片

2.创建和更新联系人

除了检索联系人之外,您还可以使用CNMutableContactCNSaveRequest类创建和更新现有联系人。 打开AddContactViewController.swift并将contact属性替换为以下实现:

var contact: CNContact {
    get {
        let store = CNContactStore()
        
        let contactToAdd = CNMutableContact()
        contactToAdd.givenName = self.firstName.text ?? ""
        contactToAdd.familyName = self.lastName.text ?? ""
        
        let mobileNumber = CNPhoneNumber(stringValue: (self.mobileNumber.text ?? ""))
        let mobileValue = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: mobileNumber)
        contactToAdd.phoneNumbers = [mobileValue]
        
        let email = CNLabeledValue(label: CNLabelHome, value: (self.homeEmail.text ?? ""))
        contactToAdd.emailAddresses = [email]
        
        if let image = self.contactImage.image {
            contactToAdd.imageData = UIImagePNGRepresentation(image)
        }
        
        let saveRequest = CNSaveRequest()
        saveRequest.addContact(contactToAdd, toContainerWithIdentifier: nil)
        
        do {
            try store.executeSaveRequest(saveRequest)
        } catch {
            print(error)
        }
        
        return contactToAdd
    }
}

我们创建了一个CNMutableContact对象,并分配一个givenNamefamilyName它。 请注意,我们使用?? 或零合并运算符。 如果值在?? 运算符为nil ,而是分配了右侧的值。

我们创建一个CNPhoneNumber对象,以表示在文本字段中输入的手机号码。 然后,我们将此数字放入带有常量CNLabelPhoneNumberMobile标签的CNLabeledValue对象中。 需要使用CNPhoneNumber类,因为可以在许多地区以多种不同的方式设置电话号码的格式。 此类采用字符串并创建电话号码值,然后Contacts框架的其余部分可以使用该电话号码。 然后将mobileValue放入数组,并分配给可变联系人的phoneNumbers属性。

我们为电子邮件创建类似的CNLabeledValue ,并为其指定CNLabelHome标签。 然后将此值分配给联系人的emailAddresses属性。 我们检查是否已将图像分配给联系人,如果已分配,则将其原始数据分配给联系人的imageData属性。

为了保存联系人,我们创建一个CNSaveRequest对象,并调用其addContact(_:toContainerWithIdentifier:)方法来告知Contacts框架我们要创建一个新联系人。 通过将nil作为标识符传递,新联系人将保存在默认联系人组中。

在另一个do-catch语句中,我们告诉联系人存储区执行保存请求。 最后,我们返回新创建的联系人以在应用程序的其余部分中使用。

生成并运行您的应用程序,然后单击右上角的加号按钮以添加新联系人。 填写表格,添加照片,然后点击完成 然后,您的新联系人应添加到主视图控制器的表视图中,如下所示。

iOS 9:联系人框架简介_第4张图片
iOS 9:联系人框架简介_第5张图片

更新现有联系人与创建新联系人非常相似。 虽然我们不会在应用程序中实现此功能,但用于更新现有联系人的代码将类似于以下内容:

let contactToUpdate = existingContact.mutableCopy()
contactToUpdate.emailAddresses.append(CNLabeledValue(label: CNLabelWork, value: emailToAdd))

let saveRequest = CNSaveRequest()
saveRequest.updateContact(contactToUpdate)
try store.executeSaveRequest(saveRequest)

3.联系人选择器视图控制器

正如我在本教程的第一部分中提到的那样,Contacts框架没有用于直接访问用户设备上每个联系人的API。 这是为了保护用户的隐私,以使应用程序无法阅读其所有联系人并收集信息。

幸运的是,该框架提供了一个UIViewController子类CNContactPickerViewController ,该子类使用户可以访问设备上存储的所有联系人。

重新访问MasterViewController.swift并在顶部为ContactUI添加import语句。

import ContactsUI

接下来,使MasterViewController类符合CNContactPickerDelegate协议。

class MasterViewController: UITableViewController, CNContactPickerDelegate

用以下内容替换MasterViewController类的addExistingContact()方法的实现:

func addExistingContact() {
    let contactPicker = CNContactPickerViewController()
    contactPicker.delegate = self
    self.presentViewController(contactPicker, animated: true, completion: nil)
}

最后,将以下CNContactPickerDelegate协议方法添加到MasterViewController类:

func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
    NSNotificationCenter.defaultCenter().postNotificationName("addNewContact", object: nil, userInfo: ["contactToAdd": contact])
}

上一次构建并运行您的应用,然后单击左上角的添加现有 应该显示类似于以下内容的视图控制器:


如果在联系人选择器视图控制器中选择了一个联系人,则视图控制器将关闭,并将所选的联系人添加到主视图控制器的表格视图中。

联系人选择器视图控制器还支持多个选择,具体取决于委托实现的方法。 还可以对其进行自定义以访问特定属性,并基于谓词过滤显示的联系人。 有关更多信息,我建议阅读CNContactPickerViewController 类参考和CNContactPickerDelegate 协议参考 。

结论

如您所见,iOS 9,OS X El Capitan和watchOS 2中的新Contacts框架是一个设计良好且易于使用的API集合。 现在,您应该可以轻松访问,创建和更新用户设备上的联系人。 与往常一样,请务必在下面的评论中留下您的评论和反馈。

翻译自: https://code.tutsplus.com/tutorials/ios-9-an-introduction-to-the-contacts-framework--cms-25599

你可能感兴趣的:(iOS 9:联系人框架简介)