Compartir a través de


Contacts y ContactsUI en Xamarin.iOS

En este artículo se describe cómo trabajar con los nuevos marcos de contactos y de interfaz de usuario de contactos en una aplicación de Xamarin.iOS. Estos marcos reemplazan la interfaz de usuario de libreta de direcciones y la libreta de direcciones existentes que se usaban en versiones anteriores de iOS.

Con la introducción de iOS 9, Apple ha lanzado dos marcos nuevos, Contacts y ContactsUI, que reemplazan los marcos de interfaz de usuario de libreta de direcciones y libreta de direcciones existentes usados por iOS 8 y versiones anteriores.

Los dos marcos nuevos contienen la funcionalidad siguiente:

  • Contacts: proporciona acceso a los datos de la lista de contactos del usuario. Como la mayoría de las aplicaciones solo necesitan acceso de solo lectura, este marco se ha optimizado para el acceso seguro para subprocesos y de solo lectura.

  • ContactsUI: proporciona elementos de la interfaz de usuario de Xamarin.iOS para mostrar, editar, seleccionar y crear contactos en dispositivos iOS.

Una hoja de contactos de ejemplo en un dispositivo iOS

Importante

Los marcos AddressBook y AddressBookUI existentes que se usan en iOS 8 (y versiones anteriores) han quedado en desuso en iOS 9 y deben reemplazarse por los marcos nuevos Contacts y ContactsUI lo antes posible para cualquier aplicación de Xamarin.iOS existente. Las nuevas aplicaciones deben escribirse en los nuevos marcos.

En las secciones siguientes, veremos estos nuevos marcos y cómo implementarlos en una aplicación de Xamarin.iOS.

Marco de Contacts

El marco de Contacts proporciona acceso de Xamarin.iOS a la información de contacto del usuario. Como la mayoría de las aplicaciones solo necesitan acceso de solo lectura, este marco se ha optimizado para el acceso seguro para subprocesos y de solo lectura.

Objetos de contacto

La clase CNContact proporciona acceso seguro para subprocesos y de solo lectura a las propiedades de un contacto, como Nombre, Dirección o Números telefónicos. CNContact funciona como un NSDictionary y contiene varias colecciones de propiedades de solo lectura (como direcciones o números telefónicos):

Información general sobre el objeto de contacto

Para cualquier propiedad que pueda tener varios valores (como la dirección de correo electrónico o los números telefónicos), se representarán como una matriz de NSLabeledValue objetos. NSLabeledValue es una tupla segura para subprocesos que consta de un conjunto de etiquetas y valores de solo lectura en el que la etiqueta define el valor para el usuario (por ejemplo, correo electrónico personal o de trabajo). El marco de Contacts proporciona una selección de etiquetas predefinidas (a través de las clases CNLabelKey y CNLabelPhoneNumberKey estáticas) que puede usar en la aplicación o tiene la opción de definir etiquetas personalizadas para sus necesidades.

Para cualquier aplicación de Xamarin.iOS que necesite ajustar los valores de un contacto existente (o crear nuevos), use la versión NSMutableContact de la clase y sus subclases (como CNMutablePostalAddress).

Por ejemplo, el código siguiente creará un nuevo contacto y lo agregará a la colección de contactos del usuario:

// 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);
}

Si este código se ejecuta en un dispositivo iOS 9, se agregará un nuevo contacto a la colección del usuario. Por ejemplo:

Nuevo contacto agregado a la colección del usuario

Formato de contacto y localización

El marco de Contacts contiene varios objetos y métodos que pueden ayudarle a dar formato y localizar contenido para mostrarlo al usuario. Por ejemplo, el código siguiente daría formato correcto a un nombre de contactos y una dirección postal para su presentación:

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

En el caso de las etiquetas de propiedades que se mostrarán en la interfaz de usuario de la aplicación, el marco de Contacts también tiene métodos para localizar esas cadenas. De nuevo, esto se basa en la configuración regional actual del dispositivo iOS en el que se ejecuta la aplicación. Por ejemplo:

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

Captura de contactos existentes

Mediante una instancia de la clase CNContactStore, puede capturar información de contacto de la base de datos de contactos del usuario. El CNContactStore contiene todos los métodos necesarios para capturar o actualizar contactos y grupos de la base de datos. Dado que estos métodos son sincrónicos, se recomienda ejecutarlos en un subproceso en segundo plano para evitar que bloquee la interfaz de usuario.

Mediante predicados (compilados a partir de la clase CNContact), puede filtrar los resultados devueltos al capturar contactos de la base de datos. Para capturar solo los contactos que contienen la cadena Appleseed, use el código siguiente:

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

Importante

El marco de Contacts no admite predicados genéricos y compuestos.

Por ejemplo, para limitar la captura a solo las propiedades GivenName y FamilyName del contacto, use el código siguiente:

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

Por último, para buscar en la base de datos y devolver los resultados, use el código siguiente:

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

Si este código se ejecutó después del ejemplo que creamos en la sección objeto de contactos anterior, devolvería el contacto "John Appleseed" que acabamos de crear.

Privacidad de acceso de contacto

Dado que los usuarios finales pueden conceder o denegar el acceso a su información de contacto por aplicación, la primera vez que realice una llamada a CNContactStore, se mostrará un cuadro de diálogo en el que se les pedirá que permitan el acceso a la aplicación.

La solicitud de permiso solo se presentará una vez, la primera vez que se ejecute la aplicación y las siguientes ejecuciones o llamadas a CNContactStore usarán el permiso seleccionado por el usuario en ese momento.

Debe diseñar la aplicación para que controle correctamente el usuario al denegar el acceso a su base de datos de contactos.

Captura de contactos parciales

Un contacto parcial es un contacto para el que solo se han capturado algunas de las propiedades disponibles del almacén de contactos. Si intenta acceder a una propiedad que no se ha capturado previamente, se producirá una excepción.

Puede comprobar fácilmente si un contacto determinado tiene la propiedad deseada mediante los métodos IsKeyAvailable o AreKeysAvailable de la instancia CNContact. Por ejemplo:

// 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);
}

Importante

Los métodos GetUnifiedContact y GetUnifiedContacts de la clase CNContactStoresolo devuelven un contacto parcial limitado a las propiedades solicitadas a partir de las claves de captura proporcionadas.

Contactos unificados

Un usuario puede tener diferentes orígenes de información de contacto para una sola persona en su base de datos de contactos (como iCloud, Facebook o Google Mail). En las aplicaciones iOS y OS X, esta información de contacto se vinculará automáticamente y se mostrará al usuario como un única, Unified Contact:

Introducción a los contactos unificados

Este contacto unificado es una vista temporal y en memoria de la información de contacto del vínculo al que se le proporcionará su propio identificador único (que se debe usar para volver a capturar el contacto si es necesario). De forma predeterminada, el marco de Contacts devolverá un contacto unificado siempre que sea posible.

Creación y actualización de contactos

Como vimos en la sección Objetos de contacto anterior, se usa un CNContactStore y una instancia de un CNMutableContact para crear nuevos contactos que se escriben en la base de datos de contactos del usuario mediante 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);
}

También se puede usar un CNSaveRequest para almacenar en caché varios cambios de contacto y grupo en una sola operación y procesar por lotes esas modificaciones en CNContactStore.

Para actualizar un contacto no mutable obtenido de una operación de captura, primero debe solicitar una copia mutable que, a continuación, se modifique y guarde de nuevo en el almacén de contactos. Por ejemplo:

// 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);
}

Notificaciones de cambio de contacto

Cada vez que se modifica un contacto, el almacén de contactos publica una CNContactStoreDidChangeNotification en el centro de notificaciones predeterminado. Si ha almacenado en caché algún contacto o actualmente lo está mostrando, deberá actualizar esos objetos desde el almacén de contactos (CNContactStore).

Contenedores y grupos

Los contactos de un usuario pueden existir localmente en el dispositivo del usuario o como contactos sincronizados con el dispositivo desde una o varias cuentas de servidor (como Facebook o Google). Cada grupo de contactos tiene su propio contenedor y un contacto determinado solo puede existir en un contenedor.

Introducción a contenedores y grupos

Algunos contenedores permiten organizar contactos en uno o varios grupos o subgrupos. Este comportamiento depende de la memoria auxiliar de un contenedor determinado. Por ejemplo, iCloud solo tiene un contenedor, pero puede tener muchos grupos (pero no subgrupos). Microsoft Exchange, por otro lado, no admite grupos, pero puede tener varios contenedores (uno para cada carpeta de Exchange).

Superposición en contenedores y grupos

Marco de ContactsUI

En situaciones en las que la aplicación no necesita presentar una interfaz de usuario personalizada, puede usar el marco de ContactsUI para presentar elementos de la interfaz de usuario para mostrar, editar, seleccionar y crear contactos en la aplicación de Xamarin.iOS.

Al usar los controles integrados de Apple, no solo se reduce la cantidad de código que tiene que crear para admitir contactos en la aplicación de Xamarin.iOS, sino que presenta una interfaz coherente para los usuarios de la aplicación.

Controlador de vista de selector de contactos

El controlador de vista de selector de contactos (CNContactPickerViewController) administra la vista de selector de contactos estándar que permite al usuario seleccionar un contacto o una propiedad de contacto de la base de datos de contactos del usuario. El usuario puede seleccionar uno o varios contactos (según su uso) y el controlador de vista de selector de contactos no solicita permiso antes de mostrar el selector.

Antes de llamar a la clase CNContactPickerViewController, defina las propiedades que puede seleccionar el usuario y defina los predicados para controlar la presentación y la selección de propiedades de contacto.

Use una instancia de la clase que hereda de CNContactPickerDelegate para responder a la interacción del usuario con el selector. Por ejemplo:

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

Para permitir que el usuario seleccione una dirección de correo electrónico de los contactos de su base de datos, puede usar el código siguiente:

// 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);

Controlador de vista de contacto

La clase del controlador de vista de contacto (CNContactViewController) proporciona un controlador para presentar una vista de contacto estándar al usuario final. La vista de contacto puede mostrar contactos nuevos, desconocidos o existentes y el tipo debe especificarse antes de que se muestre la vista llamando al constructor estático correcto (FromNewContact, FromUnknownContact, FromContact). Por ejemplo:

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

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

Resumen

En este artículo se ha analizado detalladamente como se trabajar con los marcos de contacto y de interfaz de usuario de contacto en una aplicación de Xamarin.iOS. En primer lugar, se trataron los diferentes tipos de objetos que proporciona el marco de Contact y cómo los usa para crear contactos nuevos o acceder a los ya existentes. También ha examinado el marco de interfaz de usuario de contacto para seleccionar contactos existentes y mostrar información de contacto.