EventKit en Xamarin.iOS

iOS tiene dos aplicaciones relacionadas con el calendario integradas: la aplicación de calendario y la aplicación recordatorios. Es bastante sencillo comprender cómo la aplicación de calendario administra los datos del calendario, pero la aplicación de recordatorios es menos obvia. Los recordatorios pueden tener fechas asociadas a ellas en términos de cuándo vencieron, cuando se completan, etc. Por lo tanto, iOS almacena todos los datos del calendario, ya sea eventos de calendario o recordatorios, en una ubicación, denominada Base de datos de calendario.

El marco EventKit proporciona una manera de acceder a los datos de Calendarios, Eventos de calendario y Recordatorios que almacena la base de datos de calendarios. El acceso a los calendarios y eventos de calendario ha estado disponible desde iOS 4, pero el acceso a los recordatorios es una novedad en iOS 6.

En esta guía trataremos los siguientes temas:

  • Conceptos básicos de EventKit: se presentan las partes fundamentales de EventKit a través de las clases principales y se proporciona una comprensión de su uso. Es necesario leer esta sección antes de abordar la siguiente parte del documento.
  • Tareas comunes: la sección de tareas comunes está pensada para ser una referencia rápida sobre cómo hacer cosas comunes, como enumerar calendarios, crear, guardar y recuperar eventos de calendario y recordatorios, así como usar los controladores integrados para crear y modificar eventos de calendario. No es necesario leer esta sección de principio a fin, ya que el objetivo es que sirva de referencia para tareas concretas.

Todas las tareas de esta guía están disponibles en la aplicación de ejemplo complementaria:

The companion sample application screens

Requisitos

EventKit se introdujo en iOS 4.0, pero el acceso a los datos de los recordatorios se introdujo en iOS 6.0. Por lo tanto, para desarrollar con EventKit de forma general, deberá dirigirse al menos a la versión 4.0, y a la 6.0 para los recordatorios.

Además, la aplicación Recordatorios no está disponible en el simulador, lo que significa que los datos de recordatorios tampoco estarán disponibles, a menos que los agregue primero. Además, las solicitudes de acceso solo se muestran al usuario en el dispositivo real. Por lo tanto, el desarrollo con EventKit se prueba mejor en el dispositivo.

Conceptos básicos de EventKit

Al trabajar con EventKit, es importante comprender las clases comunes y su uso. Todas estas clases se pueden encontrar en EventKit y EventKitUI (para EKEventEditController).

EventStore

La clase EventStore es la clase más importante de EventKit porque es necesaria para realizar cualquier operación en EventKit. Se puede considerar como el almacenamiento persistente, o el motor de base de datos, de todos los datos de EventKit. Desde EventStore tiene acceso tanto a los calendarios como a los eventos de calendario en la aplicación Calendario, así como a los recordatorios de la aplicación Recordatorios.

Dado que EventStore es como un motor de base de datos, debe durar, lo que significa que debe crearse y destruirse lo menos posible durante la vigencia de una instancia de aplicación. De hecho, se recomienda que, una vez que cree una instancia de EventStore en una aplicación, mantenga esa referencia durante toda la vigencia de la aplicación, a menos que esté seguro de que no la necesitará de nuevo. Además, todas las llamadas deben ir a una sola instancia de EventStore. Por este motivo, se recomienda el patrón Singleton para mantener una sola instancia disponible.

Creación de un almacén de eventos

El código siguiente muestra una manera eficaz de crear una única instancia de la clase EventStore y hacer que esté disponible estáticamente desde una aplicación:

public class App
{
    public static App Current {
            get { return current; }
    }
    private static App current;

    public EKEventStore EventStore {
            get { return eventStore; }
    }
    protected EKEventStore eventStore;

    static App ()
    {
            current = new App();
    }
    protected App () 
    {
            eventStore = new EKEventStore ( );
    }
}

El código anterior usa el patrón Singleton para crear una instancia de EventStore cuando se carga la aplicación. Es entonces cuando se puede acceder globalmente a EventStore desde la aplicación de la siguiente manera:

App.Current.EventStore;

Tenga en cuenta que todos los ejemplos de aquí usan este patrón, por lo que hacen referencia a App.Current.EventStore mediante EventStore.

Solicitud de acceso a los datos de calendarios y recordatorios

Antes de poder acceder a los datos a través de EventStore, una aplicación debe solicitar primero acceso a los datos de eventos de calendario o los datos de recordatorios, según cuál necesite. Para facilitar esto, EventStore expone un método denominado RequestAccess que, cuando se le llama, muestra una vista de alerta al usuario que le indica que la aplicación solicita acceso a los datos de calendarios o a los datos de recordatorios, según qué EKEntityType se le pase. Dado que se genera una vista de alerta, la llamada es asincrónica y llamará a un controlador de finalización pasado como NSAction (o Lambda) a esta, que recibirá dos parámetros: un valor booleano de si se concedió o no acceso, y un valor NSError, que, si no es nulo, contendrá cualquier información de error en la solicitud. Por ejemplo, el código siguiente solicitará acceso a los datos del evento de calendario y mostrará una vista de alerta si no se ha concedido la solicitud.

App.Current.EventStore.RequestAccess (EKEntityType.Event, 
    (bool granted, NSError e) => {
            if (granted)
                    //do something here
            else
                    new UIAlertView ( "Access Denied", 
"User Denied Access to Calendar Data", null,
"ok", null).Show ();
            } );

Una vez que se haya concedido la solicitud, se recordará siempre y cuando la aplicación esté instalada en el dispositivo, y no le aparecerá una alerta al usuario. Sin embargo, el acceso solo se concede al tipo de recurso, ya sea eventos de calendario o recordatorios concedidos. Si una aplicación necesita acceso a ambos, debe solicitar ambos.

Dado que se recuerda el permiso, es relativamente barato realizar la solicitud cada vez, por lo que es una buena idea solicitar siempre el acceso antes de realizar una operación.

Además, dado que se llama al controlador de finalización en un subproceso distinto (que no es de interfaz de usuario), todas las actualizaciones de la interfaz de usuario en el controlador de finalización se deben llamar a través de InvokeOnMainThread; de lo contrario, se producirá una excepción y, si no se detecta, la aplicación se bloqueará.

EKEntityType

EKEntityType es una enumeración que describe el tipo de elemento o datos de EventKit. Tiene dos valores: Event y Reminder. Se usa en varios métodos, incluido EventStore.RequestAccess para indicar a EventKit a qué tipo de datos obtener acceso o qué tipo de datos recuperar.

EKCalendar

EKCalendar representa un calendario, que contiene un grupo de eventos de calendario. Los calendarios se pueden almacenar en muchos lugares diferentes, como localmente, en iCloud, en una ubicación de proveedor de terceros, como Exchange Server o Google, entre otros. Muchas veces EKCalendar se usa para indicar a EventKit dónde buscar eventos o dónde guardarlos.

EKEventEditController

EKEventEditController se puede encontrar en el espacio de nombres EventKitUI y es un controlador integrado que se puede usar para editar o crear eventos de calendario. Al igual que los controladores de cámara integrados, EKEventEditController realiza el trabajo pesado por usted a la hora de mostrar la interfaz de usuario y controlar el ahorro.

EKEvent

EKEvent representa un evento de calendario. Tanto EKEvent como EKReminder heredan de EKCalendarItem y tienen campos como Title, Notes, etc.

EKReminder

EKReminder representa un elemento de recordatorio.

EKSpan

EKSpan es una enumeración que describe el intervalo de eventos al modificar eventos que pueden repetirse y tiene dos valores: ThisEvent y FutureEvents. ThisEvent significa que los cambios solo se producirán en el evento concreto de la serie a la que se hace referencia, mientras que FutureEvents afectará a ese evento y a todas las repeticiones futuras.

Tareas

Para facilitar el uso, EventKit se ha dividido en tareas comunes, que se describen en las secciones siguientes.

Enumeración de calendarios

Para enumerar los calendarios que el usuario ha configurado en el dispositivo, llame a GetCalendars en EventStore y pase el tipo de calendarios (ya sea recordatorios o eventos) que desea recibir:

EKCalendar[] calendars = 
App.Current.EventStore.GetCalendars ( EKEntityType.Event );

Adición o modificación de un evento mediante el controlador integrado

EKEventEditViewController hace gran parte del trabajo pesado por usted si desea crear o editar un evento con la misma interfaz de usuario que se presenta al usuario al utilizar la aplicación Calendario:

The UI that is presented to the user when using the Calendar Application

Para usarlo, es conveniente declararlo como una variable de nivel de clase para que no se recopilen elementos no utilizados si se declara dentro de un método:

public class HomeController : DialogViewController
{
        protected CreateEventEditViewDelegate eventControllerDelegate;
        ...
}

A continuación, para iniciarlo: cree una instancia de él, asígnele una referencia a EventStore, conéctele un delegado EKEventEditViewDelegate y, a continuación, visualícelo mediante PresentViewController:

EventKitUI.EKEventEditViewController eventController = 
        new EventKitUI.EKEventEditViewController ();

// set the controller's event store - it needs to know where/how to save the event
eventController.EventStore = App.Current.EventStore;

// wire up a delegate to handle events from the controller
eventControllerDelegate = new CreateEventEditViewDelegate ( eventController );
eventController.EditViewDelegate = eventControllerDelegate;

// show the event controller
PresentViewController ( eventController, true, null );

Opcionalmente, si desea rellenar previamente el evento, puede crear un evento nuevo (como se muestra a continuación) o recuperar un evento guardado:

EKEvent newEvent = EKEvent.FromStore ( App.Current.EventStore );
// set the alarm for 10 minutes from now
newEvent.AddAlarm ( EKAlarm.FromDate ( DateTime.Now.AddMinutes ( 10 ) ) );
// make the event start 20 minutes from now and last 30 minutes
newEvent.StartDate = DateTime.Now.AddMinutes ( 20 );
newEvent.EndDate = DateTime.Now.AddMinutes ( 50 );
newEvent.Title = "Get outside and exercise!";
newEvent.Notes = "This is your reminder to go and exercise for 30 minutes.”;

Si desea rellenar previamente la interfaz de usuario, asegúrese de establecer la propiedad Event en el controlador:

eventController.Event = newEvent;

Para usar un evento existente, consulte la sección Recuperación de un evento por identificador más adelante.

El delegado debe invalidar el método Completed, al que llama el controlador cuando el usuario finaliza con el cuadro de diálogo:

protected class CreateEventEditViewDelegate : EventKitUI.EKEventEditViewDelegate
{
        // we need to keep a reference to the controller so we can dismiss it
        protected EventKitUI.EKEventEditViewController eventController;

        public CreateEventEditViewDelegate (EventKitUI.EKEventEditViewController eventController)
        {
                // save our controller reference
                this.eventController = eventController;
        }

        // completed is called when a user eith
        public override void Completed (EventKitUI.EKEventEditViewController controller, EKEventEditViewAction action)
        {
                eventController.DismissViewController (true, null);
                }
        }
}

Opcionalmente, en el delegado, puede comprobar la acción en el método Completed para modificar el evento y volver a guardarlo, o si se cancela, hacer otras cosas, etc.:

public override void Completed (EventKitUI.EKEventEditViewController controller, EKEventEditViewAction action)
{
        eventController.DismissViewController (true, null);

        switch ( action ) {

        case EKEventEditViewAction.Canceled:
                break;
        case EKEventEditViewAction.Deleted:
                break;
        case EKEventEditViewAction.Saved:
                // if you wanted to modify the event you could do so here,
// and then save:
                //App.Current.EventStore.SaveEvent ( controller.Event, )
                break;
        }
}

Creación de un evento mediante programación

Para crear un evento en el código, use el método Factory Method FomStore en la clase EKEvent y defina los datos en él:

EKEvent newEvent = EKEvent.FromStore ( App.Current.EventStore );
// set the alarm for 10 minutes from now
newEvent.AddAlarm ( EKAlarm.FromDate ( DateTime.Now.AddMinutes ( 10 ) ) );
// make the event start 20 minutes from now and last 30 minutes
newEvent.StartDate = DateTime.Now.AddMinutes ( 20 );
newEvent.EndDate = DateTime.Now.AddMinutes ( 50 );
newEvent.Title = "Get outside and do some exercise!";
newEvent.Notes = "This is your motivational event to go and do 30 minutes of exercise. Super important. Do this.";

Debe establecer el calendario en el que desea guardar el evento, pero si no tiene ninguna preferencia, puede usar el valor predeterminado:

newEvent.Calendar = App.Current.EventStore.DefaultCalendarForNewEvents;

Para guardar el evento, llame al método SaveEvent en EventStore:

NSError e;
App.Current.EventStore.SaveEvent ( newEvent, EKSpan.ThisEvent, out e );

Después de guardarlo, la propiedad EventIdentifier se actualizará con un identificador único que se puede usar más adelante para recuperar el evento:

Console.WriteLine ("Event Saved, ID: " + newEvent.CalendarItemIdentifier);

EventIdentifier es un GUID con formato de cadena.

Creación de un recordatorio mediante programación

Crear un recordatorio en el código es casi lo mismo que crear un evento de calendario:

EKReminder reminder = EKReminder.Create ( App.Current.EventStore );
reminder.Title = "Do something awesome!";
reminder.Calendar = App.Current.EventStore.DefaultCalendarForNewReminders;

Para guardarlo, llame al método SaveReminder en EventStore:

NSError e;
App.Current.EventStore.SaveReminder ( reminder, true, out e );

Recuperación de un evento por identificador

Para recuperar un evento por su identificador, use el método EventFromIdentifier en EventStore y pásele el valor de EventIdentifier que se extrajo del evento:

EKEvent mySavedEvent = App.Current.EventStore.EventFromIdentifier ( newEvent.EventIdentifier );

En el caso de los eventos, hay otras dos propiedades de identificador, pero EventIdentifier es la única que funciona en este caso.

Recuperación de un recordatorio por identificador

Para recuperar un recordatorio, use el método GetCalendarItem en EventStore y pásele CalendarItemIdentifier:

EKCalendarItem myReminder = App.Current.EventStore.GetCalendarItem ( reminder.CalendarItemIdentifier );

Dado que GetCalendarItem devuelve un elemento EKCalendarItem, se debe convertir a EKReminder si necesita acceder a los datos de recordatorios o usar la instancia como EKReminder posteriormente.

No use GetCalendarItem para eventos de calendario, ya que en el momento de escribirlo, no funciona.

Eliminación de un evento

Para eliminar un evento de calendario, llame a RemoveEvent en EventStore y pase una referencia al evento y el elemento EKSpan adecuado:

NSError e;
App.Current.EventStore.RemoveEvent ( mySavedEvent, EKSpan.ThisEvent, true, out e);

Sin embargo, tenga en cuenta que después de eliminar un evento, la referencia del evento será null.

Eliminación de un recordatorio

Para eliminar un recordatorio, llame a RemoveReminder en EventStore y pase una referencia al recordatorio:

NSError e;
App.Current.EventStore.RemoveReminder ( myReminder as EKReminder, true, out e);

Tenga en cuenta que en el código anterior hay una conversión a EKReminder, porque se usó GetCalendarItem para recuperarlo.

Búsqueda de eventos

Para buscar eventos de calendario, debe crear un objeto NSPredicate a través del método PredicateForEvents en EventStore. NSPredicate es un objeto de datos de consulta que iOS usa para buscar coincidencias:

DateTime startDate = DateTime.Now.AddDays ( -7 );
DateTime endDate = DateTime.Now;
// the third parameter is calendars we want to look in, to use all calendars, we pass null
NSPredicate query = App.Current.EventStore.PredicateForEvents ( startDate, endDate, null );

Una vez creado NSPredicate, use el método EventsMatching en EventStore:

// execute the query
EKCalendarItem[] events = App.Current.EventStore.EventsMatching ( query );

Tenga en cuenta que las consultas son sincrónicas (bloqueantes) y pueden tardar mucho tiempo, según la consulta, por lo que es posible que quiera iniciar un nuevo subproceso o tarea para hacerlo.

Búsqueda de recordatorios

La búsqueda de recordatorios es similar a la de los eventos; requiere un predicado, pero la llamada ya es asincrónica, por lo que puede olvidarse de bloquear el subproceso:

// create our NSPredicate which we'll use for the query
NSPredicate query = App.Current.EventStore.PredicateForReminders ( null );

// execute the query
App.Current.EventStore.FetchReminders (
        query, ( EKReminder[] items ) => {
                // do someting with the items
        } );

Resumen

En este documento se proporciona información general sobre las partes importantes del marco EventKit y una serie de las tareas más comunes. Sin embargo, el marco EventKit es muy grande y eficaz, e incluye características que no se han presentado aquí, como actualizaciones por lotes, configuración de alarmas, configuración de repeticiones en eventos, registro y escucha de cambios en la base de datos de calendarios, configuración de GeoFences y muchas más. Para más información, consulte la Guía de programación de calendarios y recordatorios de Apple.