Xamarin.iOS 中的 Contacts 和 ContactsUI

本文介绍如何在 Xamarin.iOS 应用中使用新的 Contacts 和 Contacts UI 框架。 这些框架取代了在早期版本的 iOS 中使用的现有 Address Book 和 Address Book UI。

随着 iOS 9 的引入,Apple 发布了两个新的框架(ContactsContactsUI),取代 iOS 8 和更早版本使用的现有通讯簿和通讯簿 UI 框架。

这两个新框架包含以下功能:

  • Contacts - 提供对用户的联系人列表数据的访问权限。 由于大多数应用只需要只读访问权限,因此此框架已针对线程安全的只读访问进行了优化。

  • ContactsUI - 提供用于在 iOS 设备上显示、编辑、选择和创建联系人的 Xamarin.iOS UI 元素。

iOS 设备上的联系表单示例

重要

iOS 8(及更早版本)使用的现有 AddressBookAddressBookUI 框架在 iOS 9 中已弃用,应尽快替换为任何现有 Xamarin.iOS 应用的新 Contacts 框架和 ContactsUI 框架。 应针对新框架编写新应用。

在以下部分中,我们将介绍这些新框架以及如何在 Xamarin.iOS 应用中实现它们。

Contacts 框架

Contacts 框架为 Xamarin.iOS 提供对用户的联系人信息的访问权限。 由于大多数应用只需要只读访问权限,因此此框架已针对线程安全的只读访问进行了优化。

联系人对象

CNContact 类提供对联系人的属性(如姓名、地址或电话号码)的线程安全只读访问权限。 CNContact 类似于 NSDictionary,其中包含属性(如地址或电话号码)的多个只读集合:

联系人对象概述

对于可具有多个值(如电子邮件地址或电话号码)的任何属性,它们将表示为 NSLabeledValue 对象的数组。 NSLabeledValue 是一个线程安全元组,由一组只读标签和值组成,其中标签向用户定义值(例如家庭或工作电子邮件)。 Contacts 框架提供了一系列预定义标签(通过 CNLabelKeyCNLabelPhoneNumberKey 静态类),可以在应用中使用这些标签,或者选择根据需要定义自定义标签。

对于任何需要调整现有联系人值(或创建新联系人)的 Xamarin.iOS 应用,请使用该类的 NSMutableContact 版本及其子类(例如 CNMutablePostalAddress)。

例如,以下代码将创建新的联系人并将其添加到用户的联系人集合中:

// Create a new Mutable Contact (read/write)
var contact = new CNMutableContact();

// Set standard properties
contact.GivenName = "John";
contact.FamilyName = "Appleseed";

// Add email addresses
var homeEmail = new CNLabeledValue<NSString>(CNLabelKey.Home, new NSString("john.appleseed@mac.com"));
var workEmail = new CNLabeledValue<NSString>(CNLabelKey.Work, new NSString("john.appleseed@apple.com"));
contact.EmailAddresses = new CNLabeledValue<NSString>[] { homeEmail, workEmail };

// Add phone numbers
var cellPhone = new CNLabeledValue<CNPhoneNumber>(CNLabelPhoneNumberKey.iPhone, new CNPhoneNumber("713-555-1212"));
var workPhone = new CNLabeledValue<CNPhoneNumber>("Work", new CNPhoneNumber("408-555-1212"));
contact.PhoneNumbers = new CNLabeledValue<CNPhoneNumber>[] { cellPhone, workPhone };

// Add work address
var workAddress = new CNMutablePostalAddress()
{
    Street = "1 Infinite Loop",
    City = "Cupertino",
    State = "CA",
    PostalCode = "95014"
};
contact.PostalAddresses = new CNLabeledValue<CNPostalAddress>[] { new CNLabeledValue<CNPostalAddress>(CNLabelKey.Work, workAddress) };

// Add birthday
var birthday = new NSDateComponents()
{
    Day = 1,
    Month = 4,
    Year = 1984
};
contact.Birthday = birthday;

// Save new contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.AddContact(contact, store.DefaultContainerIdentifier);

// Attempt to save changes
NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error))
{
    Console.WriteLine("New contact saved");
}
else
{
    Console.WriteLine("Save error: {0}", error);
}

如果此代码在 iOS 9 设备上运行,则会将新联系人添加到用户的集合中。 例如:

添加到用户集合的新联系人

联系人格式化和本地化

Contacts 框架包含多个对象和方法,可帮助你格式化和本地化要显示给用户的内容。 例如,以下代码将正确格式化联系人姓名和邮件地址以供显示:

Console.WriteLine(CNContactFormatter.GetStringFrom(contact, CNContactFormatterStyle.FullName));
Console.WriteLine(CNPostalAddressFormatter.GetStringFrom(workAddress, CNPostalAddressFormatterStyle.MailingAddress));

对于将在应用的 UI 中显示的属性标签,Contact 框架也有用于本地化这些字符串的方法。 同样,这基于运行应用的 iOS 设备的当前区域设置。 例如:

// Localized properties
Console.WriteLine(CNContact.LocalizeProperty(CNContactOptions.Nickname));
Console.WriteLine(CNLabeledValue<NSString>.LocalizeLabel(CNLabelKey.Home));

提取现有联系人

通过使用 CNContactStore 类的实例,可以从用户的联系人数据库中提取联系人信息。 CNContactStore 包含从数据库提取或更新联系人和组所需的所有方法。 由于这些方法是同步的,因此建议在后台线程中运行它们,以免阻止 UI。

通过使用谓词(从 CNContact 类生成),可以筛选从数据库中提取联系人时返回的结果。 若要仅提取包含字符串 Appleseed 的联系人,请使用以下代码:

// Create predicate to locate requested contact
var predicate = CNContact.GetPredicateForContacts("Appleseed");

重要

Contacts 框架不支持泛型谓词和复合谓词。

例如,若要仅提取联系人的 GivenName 和 FamilyName 属性,请使用以下代码:

// Define fields to be searched
var fetchKeys = new NSString[] {CNContactKey.GivenName, CNContactKey.FamilyName};

最后,若要搜索数据库并返回结果,请使用以下代码:

// Grab matching contacts
var store = new CNContactStore();
NSError error;
var contacts = store.GetUnifiedContacts(predicate, fetchKeys, out error);

如果此代码在上面的“联系人对象”部分中创建的示例之后运行,它将返回刚刚创建的“John Appleseed”联系人。

联系人访问隐私

由于最终用户可以根据每个应用程序授予或拒绝对其联系信息的访问权限,因此首次调用 CNContactStore 时,将显示一个对话框,要求他们允许访问你的应用。

权限请求将仅显示一次,首次运行应用时,对 CNContactStore 的后续运行或调用将使用用户当时选择的权限。

你应该对你的应用进行设计,以便它简洁地处理拒绝访问其联系人数据库的用户。

提取部分联系人

“部分联系人”是仅从联系人存储中提取了部分可用属性的联系人。 如果你尝试访问之前未提取的属性,则会导致异常。

可以使用 CNContact 实例的 IsKeyAvailableAreKeysAvailable 方法轻松检查给定联系人是否具有所需属性。 例如:

// Does the contact contain the requested key?
if (!contact.IsKeyAvailable(CNContactOption.PostalAddresses)) {
    // No, re-request to pull required info
    var fetchKeys = new NSString[] {CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.PostalAddresses};
    var store = new CNContactStore();
    NSError error;
    contact = store.GetUnifiedContact(contact.Identifier, fetchKeys, out error);
}

重要

CNContactStore 类的 GetUnifiedContactGetUnifiedContacts 方法仅返回仅限于根据所提供的提取密钥请求的属性的部分联系人。

统一联系人

用户的联系人数据库中,对于同一个人员,可能有不同来源的联系人信息(如 iCloud、Facebook 或 Google Mail)。 在 iOS 和 OS X 应用中,这些联系信息将自动链接在一起,并作为单个统一联系人显示给用户:

统一联系人概述

此统一联系人是链接联系信息的临时内存中视图,该视图将具有其自己的唯一标识符(如果需要,该标识符应用于重新提取联系人)。 默认情况下,Contacts 框架将尽可能返回统一联系人。

创建和更新联系人

正如我们在上面的联系人对象部分中看到的,可以使用 CNContactStoreCNMutableContact 的实例来创建新联系人,然后使用 CNSaveRequest 将这些联系人写入到用户的联系人数据库:

// Create a new Mutable Contact (read/write)
var contact = new CNMutableContact();

// Set standard properties
contact.GivenName = "John";
contact.FamilyName = "Appleseed";

// Save new contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.AddContact(contact, store.DefaultContainerIdentifier);

NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error)) {
    Console.WriteLine("New contact saved");
} else {
    Console.WriteLine("Save error: {0}", error);
}

CNSaveRequest 还可用于将多个联系人和组更改缓存到单个操作中,并将这些修改批处理到 CNContactStore

若要更新从提取操作获取的非可变联系人,必须先请求可变副本,然后修改并保存回联系人存储。 例如:

// Get mutable copy of contact
var mutable = contact.MutableCopy() as CNMutableContact;
var newEmail = new CNLabeledValue<NSString>(CNLabelKey.Home, new NSString("john.appleseed@xamarin.com"));

// Append new email
var emails = new NSObject[mutable.EmailAddresses.Length+1];
mutable.EmailAddresses.CopyTo(emails,0);
emails[mutable.EmailAddresses.Length+1] = newEmail;
mutable.EmailAddresses = emails;

// Update contact
var store = new CNContactStore();
var saveRequest = new CNSaveRequest();
saveRequest.UpdateContact(mutable);

NSError error;
if (store.ExecuteSaveRequest(saveRequest, out error)) {
    Console.WriteLine("Contact updated.");
} else {
    Console.WriteLine("Update error: {0}", error);
}

联系人更改通知

每当修改联系人时,联系人存储都会将 CNContactStoreDidChangeNotification 发布到默认通知中心。 如果已缓存或当前正在显示任何联系人,则需要从联系人存储 (CNContactStore) 刷新这些对象。

容器和组

用户的联系人既可以本地存在于用户设备上,也可以是从一个或多个服务器帐户(如 Facebook 或 Google)同步到设备的联系人。 每个联系人池都有自己的容器,给定联系人只能存在于一个容器中。

容器和组概述

某些容器允许将联系人排入一个或多个组或子组中。 此行为依赖于给定容器的后备存储。 例如,iCloud 只有一个容器,但可以有多个组(但没有子组)。 另一方面,Microsoft Exchange 不支持组,但可以有多个容器(每个 Exchange 文件夹都有一个容器)。

容器和组中的重叠

ContactsUI 框架

对于应用程序不需要显示自定义 UI 的情况,可以使用 ContactsUI 框架来显示、编辑、选择和创建 Xamarin.iOS 应用中的联系人。

通过使用 Apple 的内置控件,不仅减少了在 Xamarin.iOS 应用中为支持联系人而必须创建的代码量,而且为应用的用户提供了一致的界面。

联系人选取器视图控制器

联系人选取器视图控制器 (CNContactPickerViewController) 管理标准联系人选取器视图,该视图允许用户从用户的联系人数据库中选择联系人或联系人属性。 用户可以选择一个或多个联系人(基于其使用情况),并且联系人选取器视图控制器在显示选取器之前不会提示请求权限。

在调用 CNContactPickerViewController 类之前,定义用户可以选择和定义谓词的属性,以控制联系人属性的显示和选择。

使用继承自 CNContactPickerDelegate 的类的实例来响应用户与选取器的交互。 例如:

using System;
using System.Linq;
using UIKit;
using Foundation;
using Contacts;
using ContactsUI;

namespace iOS9Contacts
{
    public class ContactPickerDelegate: CNContactPickerDelegate
    {
        #region Constructors
        public ContactPickerDelegate ()
        {
        }

        public ContactPickerDelegate (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void ContactPickerDidCancel (CNContactPickerViewController picker)
        {
            Console.WriteLine ("User canceled picker");

        }

        public override void DidSelectContact (CNContactPickerViewController picker, CNContact contact)
        {
            Console.WriteLine ("Selected: {0}", contact);
        }

        public override void DidSelectContactProperty (CNContactPickerViewController picker, CNContactProperty contactProperty)
        {
            Console.WriteLine ("Selected Property: {0}", contactProperty);
        }
        #endregion
    }
}

若要允许用户从其数据库的联系人中选择电子邮件地址,可以使用以下代码:

// Create a new picker
var picker = new CNContactPickerViewController();

// Select property to pick
picker.DisplayedPropertyKeys = new NSString[] {CNContactKey.EmailAddresses};
picker.PredicateForEnablingContact = NSPredicate.FromFormat("emailAddresses.@count > 0");
picker.PredicateForSelectionOfContact = NSPredicate.FromFormat("emailAddresses.@count == 1");

// Respond to selection
picker.Delegate = new ContactPickerDelegate();

// Display picker
PresentViewController(picker,true,null);

联系人视图控制器

联系人视图控制器 (CNContactViewController) 类提供一个控制器,用于向最终用户显示标准联系人视图。 “联系人”视图可以显示新的、未知的或现有的联系人,并且在显示视图之前,必须通过调用正确的静态构造函数(FromNewContactFromUnknownContactFromContact)来指定类型。 例如:

// Create a new contact view
var view = CNContactViewController.FromContact(contact);

// Display the view
PresentViewController(view, true, null);

总结

本文详细介绍了如何在 Xamarin.iOS 应用程序中使用 Contact 和 Contact UI 框架。 首先,介绍了 Contact 框架提供的不同类型的对象,以及如何使用它们创建新联系人或访问现有联系人。 还检查了 Contact UI 框架,以选择现有联系人并显示联系人信息。