初窥 iOS 9 的 Contacts 框架

505 查看

contacts-framework.jpg

iOS 9为用户和开发者展示了很多新的技术和在现有技术上的优化。正如我们看到的,在这个版本里有很多第一次展示的新的内容,也有很多已有的框架和类的变化和更新。除此之外,始终惊喜的是,有一些旧的APIs被放弃和不再建议使用,为新的全新开发的或用来做过渡的APIs让位。在iOS9中的例子就是全新的Contacts framework, 它以更流行的模式来替代旧的 AddressBook 框架,更简单和直接。

以前用过AddressBook API的每个开发者都可以肯定的说它在iOS SDK中肯定不属于能简单使用的那一部分。总体上,AddressBook很难理解和掌握,而且对新手而言更是如此。这一切归咎于历史原因,而新的Contacts 框架理解起来更简单和易于使用。联系人信息可以在很短时间内被获取,创建或更新,跟联系人相关的开发时间能被很大的缩短,变更和修改可以很快被完成。

在以下的段落中我们会强调Contacts框架的最重要的部分。我不会展示太多细节,因为你可以在苹果官方文档和 WWDC 2015 session 223 video里找到相关内容。

因此,首先,我会从关键地方开始,那就是用户隐私。用户经常被一个应用询问它是否有权利获取用户的联系人信息。如果用户同意,那么应用就可以自由的同用户的联系人数据库交互。如果不同意,用户禁止APP获取联系人信息,那么这个决定必须被APP采纳并且绝对不能同联系人数据库交互。一会儿我们将会更仔细的谈论它,然后我们将会看到所有可能的场景是怎样以编程方式被处理的。不仅如此,要时刻记住用户始终有权在设备设置里更改应用是否能获取信息的权限,所以你应该在运行任何相关任务之前,经常检查你的应用是否有获取联系人信息的权利。

联系人数据的最大来源一直都是设备里的数据库。但是,当一个应用请求联系人信息时,Contacts框架不仅仅是在那里查找。实际上,它也会搜索其他来源,比如你的iCloud账户(当然如果你已经连接了的话),然后给应用返回从各个源头获取到的信息的整合。这非常有用,因为你没有必要除开搜索设备数据库以外去创建其他搜索联系人信息的方法。你在同一时间就得到了所有,然后以自己的方式使用它们。

Contacts框架有服务于各个特定的目标的类。所有的都很重要,但是有一个是使用的最多的,叫做CNContactStore。这个类以编程方式展示了联系人数据库,并且提供了许多实现不同任务的方法,例如获取,保存或者更新记录,权限检查和权限请求,很多很多。一个单独的联系人记录被CNContact类展示,但是记住这个类的特性是不可变的。如果你想创建一个新的联系人记录或者更新一个已存在的联系人记录,你必须使用CNMutableContact类。注意同Contacts框架打交道的时候,特别是获取联系人信息时,你应该始终在后台线程中运行这些任务。如果一个联系人信息获取任务花去了太多时间而且在主线程运行,那么你的应用有可能无响应,然后最终导致非常糟糕的交互体验。

当导入联系人信息到APP时,所有的联系人的属性都需要的情况是很少见的。从所有的Contacts 框架要搜索的数据源中获取所有联系人数据的操作,能被证明是一个非常耗资源的进程,所以你应该避免这样做除非你确定你是真的将会用到所有的数据片段,哪怕是最后一个。万幸的是Contacts框架提供了可以获取部分结果的方法,意味着只是联系人的一部分属性值而不是所有。例如,你可以只是请求名或姓,家庭地址,家庭电话等,通过排除你不需要的那些数据来节约很多资源。

除了Contacts框架提供的所有以编程方式获取联系人信息的方式之外,它也提供了一些可以与应用协作的默认的UI,以这种方式直接的以及可视化的访问联系人信息。提供的UI跟Contacts应用基本一样,这意味着有一个contact picker view controller 连同详细信息卡一起,可以被用来获取联系人和属性(它可以被定制化到一个级别),和一个contacts view controller 可用来展示联系人细节信息以及实现某些动作(例如,打一个电话)。

上面提到的内容的细节将会在这个指南的后面看到。再一次的,访问官方文档来获取更多我已经展示的或我将要展示的内容的信息。我们现在来看demo应用将会是什么样的,然后我们来学习Contacts框架的类。你会发现同这个新的框架交互非常简单和有趣。

Demo APP 快速浏览

通过这个教程的demo应用,我会向你展示尽量多的关于这个新框架的内容。事实上,在接下来的部分我会向你展示怎样去:

  1. 检查这个应用是否被授权获取联系人信息以及怎样发起授权请求。
  2. 使用3个不同的方法获取联系人信息。其中一个包括使用picker view controller
  3. 访问获取到的联系人的属性以及合理的格式化它们以用来展示。
  4. 使用默认Contacts ?UI 的来选取,查看, 甚至编辑联系人信息。
  5. 创建一个新的联系人记录。

我把这个demo应用命名为Birthdays,因为它的目的是对所有引入到该应用中的联系人的生日做展示。联系人的全名,照片(如果有)和家庭邮箱地址也会被展示。理想状态下,这个应用应该可以是个生日提示器,当然,我们不会处理通知,短信发送以及其它相关动作。

这个应用是基于导航模式的,而且它由下面的部分组成:

当这个应用开始时, ViewController是默认被展示的。它展示了先前我提到的所有引入的联系人的信息,并且提供了获取更多联系人信息(右上角按钮)操作,创建一个新联系人(左上角按钮),以及通过点击一行来查看联系人详细信息的方法。

t43_1_display_records.png

联系人详细信息将会展示在内嵌的联系人view controller 里。正如你将会看到的那样,你可以展示所有的属性或者只选择你感兴趣展示的那部分。

获取联系人信息在后面将会是一个很有趣的部分。我会通过3种不同的处理方式向你展示3种方法。

  1. 第一种,我们将会输入一个联系人名字(或者名字的一部分),然后通过点击键盘上的return按钮,这个应用就会获取匹配输入名字的联系人信息。
  2. 正如你将会在下面看到的截图那样,在屏幕中间有一个picker view。我们将会利用它来找到所有跟在picker里选中的月份所匹配的联系人的生日月份,而且获取操作会在点击Done按钮的时候被触发。
  3. 我们会利用框架提供的默认picker view controller来直接查看和选择联系人信息。 注意在这个controller里展示的联系人信息是可以被定制化的,picker view controller里的行为也一样。稍后你将会看到怎样做。

t43_2_fetch_contacts.png

这就是picker view controller,只展现了具有有效生日日期的联系人集合:

t43_3_picker_view_controller.png

应用的最后部分是关于创建一个新的联系人。这是个很简单的任务,归功于这个demo 应用我们将会利用下面的view controller 来键入将要被创建的联系人的姓名,家庭邮箱地址和生日(我们这里不处理图片,因为此刻它并不是最重要的)。

t43_2_fetch_contacts.png

这个demo应用的示范数据(示范联系人)将会是模拟器数据库保存的默认联系人。这些联系人信息对于实现我们的目的足够用了还非常好。当然,你可以使用你设备里面的联系人信息,或者增加新的联系人到模拟器中。默认情况下模拟器联系人信息不包含图片,但你可以轻松地在图片库里找到图片并且添加。

像往常一样,在这里下载我们将会在后面用到的作为入门的初始工程。 下载完成后,打开它然后浏览一下我们已经在里面添加了的文件。准备好后,就可以开始下面的部分了。

Contact Store类

同联系人打交道的时候有一个你一直都会用到的最基本的类就是CNContactStore 类。这个类实际上展现了存在于设备上的联系人数据库,而且负责管理所有在应用和这个实际数据库之间的所有交互。再进一步说,它管理了所有关于拉取(fetching),保存(saving)和更新(updating)联系人和群组记录的工作。简而言之,它就是大多数同联系人信息交互的初始点,在马上就要写到的代码中你们将会看到这点。

除此之外,正如我在介绍中提到的,用户隐私问题是iOS的重要组成部分,所以在处理这方面时要特别注意。普遍都了解的是用户可以在第三方应用中选择允许或者拒绝它们使用联系人信息,所以非常重要的是保证无论何时去实现联系人相关的任务的时候,你的APP都被授权了可以这么做。使用CNContactStore类,你可以查看你的应用的当前授权状态( current authorization status )。始终牢记用户可以通过Setting 来随时禁止你的应用获取联系人数据信息,尽管可能一开始你的应用是被允许访问的,所以确保你的任务是否可以操作是非常重要的,当然也要在每个不同的情形下引导你的应用朝着对的方向发展。没有被处理到的情形会最终导致糟糕的用户体验,而这恰是你必须要避免的。在这个向导中我们会严格考虑demo应用是否被授权的情况,甚至从这个部分就开始了。我们马上要做的内容,就是只要你想用就可以在你的工程里自由使用。

你将会马上看到的是在下面的场景中(区别于其他的场景) contacts store 类都是必须要用的:

  • 当获取联系人信息时
  • 当创建,保存和更新一个联系人时
  • 当使用 Contact Picker view controller来选择联系人时

牢记这条后,我们初始化一个 CNContactStore对象,而且我们会在整个类里都使用到它。另一方面,我们也可以在任何要使用它的时候创建新的对象,但是因为这个类在代码里代表了联系人数据库,有什么理由需要多个它的实例呢?所以,我们开始吧。首先打开 AppDelegate.swift 文件,初始化和声明一个 CNContactStore属性。在文件的顶部,添加下面的代码:

必须在类声明的顶部添加下面的框架:

非常好!现在,在我们处理应用的授权状态以及针对这个状态可以做的操作之前,我们先写下两个简单且方便的方法。注意它们并不是为了继续这个项目而需要的,不要它们也可以做我们的工作。但是,实现一些针对完成某个目的的小方法被证明了是非常便利的。

因此,第一个小方法就是从任何其它类中可以简单访问应用的delegate类(AppDelegate)的方法。通常,下面的代码可以实现访问应用的delegate:

但是,我个人发现每次当我需要获取app delegate时都要写下所有的以上代码,这感觉像是被中途打扰了。如果我们编写下面的这个类方法又会怎样?

通过它,我们可以通过一种更简便的方式来访问app delegate的任何属性或者方法。例如,我们可以像下面展示那样在工程中的任何类中获取contacts store属性:

第二个我们将会在文件中添加的便捷方法就是一个展示提示信息的controller,提示信息变量是通过参数传递的。实现起来并不复杂,但是有个特殊的地方要注意;一个提示controller必须被一个view controller展示,而应用的app delegate 并不是一个view controller。

为了解决这个问题,我们有必要找到当前app window上的最上层的view controller, 然后在这个view controller上展示这个提示controller。下面是实现方法:

现在我们做很重要的事了,那就是处理应用的授权状态。这个状态是被CNAuthorizationStatus枚举值展现的并且是属于CNContactStore 类。它包含以下的4种值:

  1. NotDetermined: 这个状态表示用户现在为止还没有允许或者拒绝对联系人数据库的访问。应用在设备上第一次安装时就会是这个状态。
  2. Restricted: 这个状态表示应用不仅不能访问联系人数据,而且用户也没有权限在Settings里修改这个权限。这个状态可能是其他活跃的限制条件的结果(例如. Parental control)
  3. Denied: 当应用是这个状态时,表示用户已经选择了不允许访问联系人数据信息。而这个只能被用户本人改变。
  4. Authorized: 这是每个应用的理想状态。当应用是这个状态时,它可以自由访问联系人数据库并且实现需要联系人数据的任务。

有一件事必须弄清楚:安装应用后,用户第一次(且仅是第一次)想试图与联系人数据交互(例如,获取联系人信息)时,iOS将会展示一个预定义好的提示controller来要求用户对应用授权:

t43_5_ask_authorization_alert.png

如果用户允许访问,一切都好。但是,如果用户拒绝访问,那么基于联系人信息的所有功能都不可能被执行。在我们的demo应用里,且在这个特定情景下,我们将展现一个自定义的提示信息(使用我们上面已实现好的方法)来告诉用户他必须在Settings里设置允许访问联系人信息的权限。我们将会在即将实现的新方法里处理这种情况。当然,我们也会在那个方法里考虑所有可能出现的授权状态。首先来看看这个方法是什么样的,稍后将会对它做更多讲解: