在以前开发者访问用户的联系人在他们的iOS设备上使用C API,那是个蛮痛苦的事情。直到iOS8还一直在使用,不过现在Apple推出了两个功能强大的面相对象的框架来管理用户联系人。Contacts
和ContactsUI
接下来,我们就来看看如何使用这些框架。
博客原文
初始项目
使用
ContactsUI
框架来显示和选择联系人。将联系人添加到用户的联系人列表。
搜索用户的联系人,并使用
NSPredicate
筛选。
Getting started
运行你下载的初始化项目,你将看到一个联系人的列表。
Converet friends to CNContacts
CNContact
这个类中包含了很多的联系人的属性,比如说,givenName
,familyName
,emailAddress
,imageData
等等。。。
打开我们项目中的Friend.swift
添加下边代码:
import Contacts
现在呢,扩展这个类,添加一个计算属性
extension Friend{
var contactValue:CNContact{
//1 创建一个不带参数的 `CNMutable` 联系实例
let contact = CNMutableContact()
//2更新对应的属性
contact.givenName = firstName
contact.familyName = lastName
//3 emailAddresses 是 CNLabeledValue 对象的数组。这意味着每个电子邮件地址都有一个对应的标签。有多种类型可供接触的标签,但在这种情况下,你坚持使用CNLabelWork。
contact.emailAddress = [CNLabeledValue(label:CNLabelWork,value:workEmail)]
//4如果有朋友的图片,联系人的图像数据用JPEG格式
if let profilePicture = profilePicture{
let imageData = UIImageJPEGRepresentation(profilePicture, 1)
contact.imageData = imageData
}
//5返回复制的副本
return contact.copy() as! CNContact
}
}
提示:
CNMutableContact
是CNContact
的可变类型,CNContact
的属性只可以读,而CNMutableContact
的属性可以改变。因此,你创建一个可变的联系人,设置其属性,然后完成以后,返回一个不可变的副本。需要注意的是CNContacat
是线程安全的,而CNMutableContact
不是。
Showing the contact's information
在FriendsViewController.swift
添加下边的代码:
import Contacts
import COntactsUI
extension FriendsViewController{
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
//1 使用所选单元格的索引路径来获得所选择的朋友,并将其转换到CNContact的一个实例
let friend = friendsList[indexPath.row]
let contact = friend.contactValue
//2使用forUnknownContact来初始化一个`CNContactViewController`,因为联系人不在用户的联系人存储中。你还可以自定义导航栏和标签栏的属性。
let contactViewController = CNContactViewController(forUnknownContact: contact)
contactViewController.navigationItem.title = "Profile"
contactViewController.hidesBottomBarWhenPushed = true
//3设置这两个属性为false,那么用户智能查看联系人的信息
contactViewController.allowsActions = false
contactViewController.allowsEditing = false
//4
navigationController?.pushViewController(contactViewController, animated: true)
}
}
现在你运行你的应用程序,点击一个 tableView cell 你将看到ContactsUI framework
将会展现朋友的信息。
你不能添加更多的朋友好友列表?您可以使用ContactsUI类CNContactPickerViewController让你的用户选择联系人的应用程序来使用。
Picking your friends
带开Main.storyboard
给friendsViewController
添加一个Bar Button Item
在导航栏的右侧,并且设置他的image
属性为AddButton
。
接着给他连接一个方法addFriends
在这个方法中写:
@IBAction func addFriends(sender: UIBarButtonItem) {
let contactPicker = CNContactPickerViewController()
presentViewController(contactPicker, animated: true, completion: nil)
}
然后你运行你的程序,点击按钮,就会看到下边的样子:
目前,用户无法导入联系人。当用户选择一个联系人,选择器视图控制器只显示有关联系人的详细信息。为了解决这个问题,你需要采取的CNContactPickerDelegate方法的优势。
Conforming to CNContactPickerDelegate
CNContactPickerDelegate
有五个可选的方法,现在你可能有兴趣的是contactPicker(:didSelectContacts)
。
extension FriendsViewController:CNContactPickerDelegate{
func contactPicker(picker: CNContactPickerViewController, didSelectContacts contacts: [CNContact]) {
}
}
现在FriendsViewController
遵循 CNContactPickerDelegate
这个协议,你已经有了一个空的contactPicker(_:didSelectContacts:)
方法,接下来要做的事情有:
从CNContscts创建一个新的朋友实例
增加新的朋友实例给你的朋友列表
Creating a friend from a CNContact
打开Friend.swift
添加如下的初始化方法
extension Friend{
init(contact:CNContact){
firstName = contact.givenName
lastName = contact.familyName
workEmail = contact.emailAddresses.first?.value as! String
if let imagedata = contact.imageData{
profilePicture = UIImage(data: imagedata)
}else{
profilePicture = nil
}
}
}
当您设置workEmail强制解开找到的第一个电子邮件地址。由于RWConnect使用电子邮件,以保持与您的朋友保持联系,所有的联系人必须有电子邮件地址。如果强行展开这样使你焦躁不安,不用担心- 你将在以后解决它!
Adding new friends to the friends List
接下来完成你的contactPicker(_:didSelectContacts:)
这个代理方法
extension FriendsViewController:CNContactPickerDelegate{
func contactPicker(picker: CNContactPickerViewController, didSelectContacts contacts: [CNContact]) {
let newFriends = contacts.map{Friend(contact: $0)}
for friend in newFriends{
if !friendsList.contains(friend){
friendsList.append(friend)
}
}
tableView.reloadData()
}
}
找到你的addFriends(_:)
方法,在 presentViewController(_:animated:completion:)
之前添加下边的代码
contactPicker.delegate = self
现在你来运行你的程序,来添加新的联系人到你的程序列表中。
你以为就这样完了么?NONONO还有一个小bug我们没有解决,还记得我们是强制解析 emailAddress
,如果你选的联系人没有邮箱地址的话,这个程序会crash掉。我们要来解决这个问题。
我们在这里解决的办法是让用户不能选择没有电子邮件的联系人。
在presentViewcontroller(:animated:completion)
之前添加下面的代码:
contactPicker.predicateForEnablingContact = NSPredicate(format: "emailAddress.@count > 0")
联系人选取器的predicateForEnablingContact
属性可以让你决定哪些联系人可以选择。
当你再次运行项目的时候,点击添加按钮,你会看到,如果没有电子邮件地址的联系人显示为灰色。
Saving friends to the user's contacts
我们给单元格添加一个左滑的动作,会显示奖联系人添加到联系人存储。
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let createContact = UITableViewRowAction(style: .Normal, title: "Create Contact") { (rowaction, indexPath) -> Void in
//todo: add the contact
}
createContact.backgroundColor = BlueColor
return [createContact]
}
在你访问或者修改用户的联系人,你必须得到用户的许可。
在这里你可能会疑惑,在上边使用 CNContactPickerViewController
时,你并没有索取用户的许可,这是为什么呢。这是因为CNContactPickerViewController
是一个out-of-process
。当用户选择了联系人的时候,就暗示了你允许使用他们的联系人。
Asking for permission
let contactStore = CNContactStore()
contactStore.requestAccessForEntityType(CNEntityType.Contacts, completionHandler: { (userFrantedAccess, _) -> Void in
})
在这里你创建了一个CNContactStore
实例,这代表用户的地址薄中包含所有它们的联系人,一旦你初始化联系人存储,调用实例方法 requestAccessForEntityType
完成处理需要一个布尔值,指示用户授予权限来访问他们的contacts。
Add the following method to FriendsViewController:
func presentPremissionErrorAlert(){
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let alert = UIAlertController(title: "Could Not Save Contact", message: "How an I supposed to add the contact if you didn't give me premission", preferredStyle: UIAlertControllerStyle.Alert)
let openSettingsAction = UIAlertAction(title: "Settings", style: .Default, handler: { (alert) -> Void in
//打开设置
UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
})
let dismissAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alert.addAction(openSettingsAction)
alert.addAction(dismissAction)
self.presentViewController(alert, animated: true, completion: nil)
}
}
上边的方法提供了一个警告,表示应用程序无法保存联系人用户。通过UIApplicationOpenSettingsURLString
来打开设置应用程序。
需要注意的是我们把上边的这个警告放在了主线程里来执行,这样可以更快的显示出警告。
我们完成我们的requestAccessForEntityType
闭包。
guard userFrantedAccess else{
self.presentPremissionErrorAlert()
return
}
现在在你的模拟器中来运行项目
如果你选择了不允许的话将会出现下边的警告
如果你选择了设置,那么会打开设置应用。
Saving friends to contacts
我们来创建一个保存联系人的方法。
func saveFriendToContacts(friend:Friend){
//1 首先呢我们需要一个可变的CNContact
let contact = friend.contactValue.mutableCopy() as! CNMutableContact
//2 新建一个CNSaveRequest 来更新或者删除联系人CNContactStore
let saveRequest = CNSaveRequest()
//3 接着告诉CNSaveRequest你想把新的朋友添加到用户的联系人
saveRequest.addContact(contact, toContainerWithIdentifier: nil)
do { //4 尝试着保存要求,如果该方法成功,则继续执行,你可以假设保存请求成功,否则抛出一个错误
let contactStore = CNContactStore()
try contactStore.executeSaveRequest(saveRequest)
//show success alert
}catch{
//show failure alert
}
}
接下来完成show success alert
和show failure alert
//show success alert
dispatch_async(dispatch_get_main_queue()) {
let successAlert = UIAlertController(title: "Contacts Saved", message: "nil", preferredStyle: .Alert)
successAlert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(successAlert, animated: true, completion: nil)
}
//show failure alert
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let failureAlert = UIAlertController(title: "Could Not Save Contact", message: "An Unknown error occurred.", preferredStyle: .Alert)
failureAlert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(failureAlert, animated: true, completion: nil)
})
我们在点击了cell左滑的按钮之后来执行这个方法
guard userFrantedAccess else{
self.presentPremissionErrorAlert()
return
}
let friend = self.friendsList[indexPath.row]
self.saveFriendToContacts(friend)
我们在运行项目之前需要重置一下模拟器。
之后重新运行,来保存你的朋友到你通讯录里
你可以到你的通讯录里看到你新添加的朋友
细心的同学可能会发现,如果你多次添加一个联系人的话也可以成功。我们要阻止这样的情况发生。
Checking for existing contacts
添加下面的代码到saveFriendToContacts
函数的最上边
//1
let contactFormatter = CNContactFormatter()
//2 格式化基于联系人给出的姓名字符串
let contactName = contactFormatter.stringFromContact(friend.contactValue)!
//3 用于搜索是否存在上边的姓名
let predicateForMatchingName = CNContact.predicateForContactsMatchingName(contactName)
//4
let matchingContacts = try! CNContactStore().unifiedContactsMatchingPredicate(predicateForMatchingName, keysToFetch: [])
//5 只保存没有匹配到的联系人
guard matchingContacts.isEmpty else{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let alert = UIAlertController(title: "Contact Already Exists", message: nil, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
})
return
}
完结!