iOS9 之 Contacts

dowhilenet

在以前开发者访问用户的联系人在他们的iOS设备上使用C API,那是个蛮痛苦的事情。直到iOS8还一直在使用,不过现在Apple推出了两个功能强大的面相对象的框架来管理用户联系人。ContactsContactsUI接下来,我们就来看看如何使用这些框架。

博客原文

初始项目

  1. 使用ContactsUI框架来显示和选择联系人。

  2. 将联系人添加到用户的联系人列表。

  3. 搜索用户的联系人,并使用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
    }
}

提示:CNMutableContactCNContact的可变类型,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.storyboardfriendsViewController添加一个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:)方法,接下来要做的事情有:

  1. 从CNContscts创建一个新的朋友实例

  2. 增加新的朋友实例给你的朋友列表

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

完结!

阅读 6.5k

这块显卡有点冷
迷茫的程序员的成长之路
654 声望
10 粉丝
0 条评论
654 声望
10 粉丝
文章目录
宣传栏