iOS9 之 Contacts
在以前开发者访问用户的联系人在他们的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 }
完结!