Tour d'horizon du framework EventKit sur iOS

le 09/05/2011 par Cyril Picat
Tags: Software Engineering

Depuis la semaine dernière est disponible sur l'AppStore d'Apple une nouvelle application 'powered by OCTO' : Mon Agenda Patrimonial. Application gratuite éditée par la Banque de Gestion Privée IndoSuez (BGPI), elle vous permet de connaître les événements fiscaux et patrimoniaux de l'année, que vous soyez ou non client de la Banque.

Logo de l'application Mon agenda patrimonialLogo Powered by OCTO Technology

Je ne reviendrai pas dans ce billet sur le contenu ou les fonctionnalités de cette application, mais plutôt sur une des ses fonctions annexes : l'intégration avec le calendrier de l'utilisateur. Cette intégration se fait via le framework Event Kit disponible dans le SDK d'Apple.

Si vous voulez savoir comment vous pouvez en quelques lignes de code insérer des évènements dans le calendrier de votre iPhone ou de votre entreprise (même avec Exchange), cet article est fait pour vous.

La première partie de l'article est une introduction à Event Kit avec des exemples de code pour tous les usages simples autour de l'interaction avec un calendrier. Ensuite, dans une deuxième partie, je donne mon retour d'expérience sur le framework avec notamment ses points forts et ses point faibles, selon moi.

Présentation d'Event Kit

EventKit a fait son apparition sur iOS4 et nécessite donc que votre application soit uniquement compatible avec des versions d'iOS > 4 ou que vous gériez dans l'application le cas ou le device est dans une version antérieure de l'OS (on y reviendra plus loin dans l'article).

Installation

Le framework EventKit n'est pas ajouté par défaut dans votre projet sous XCode, il faut le faire manuellement. Pour cela, procéder comme habituellement via un click droit sur Frameworks :

ajouter un framework dans XCode

Ensuite scrollez pour sélectionner EventKit.framework.

Voilà, il vous reste à inclure le header EventKit.h dans vos classes ou services qui vont accèder au calendrier et vous êtes prêts :

YourClass.m

#import <EventKit/EventKit.h>

...

Rapide aperçu du framework

EventKit est un "petit framework" d'une dizaine de classes avec 3 classes principales : EKEvent, EKEventStore et EKCalendar. En voici une description succinte :

  • EKEvent : cette classe représente un événement d'un calendrier
  • EKEventStore : cette classe est un entrepôt d'événements, c'est à elle qu'on s'adresse pour récupérer ou créér un événement
  • EKCalendar : cette classe représente un calendrier disponible sur votre iPhone. Attention un point important est que dans le design Apple les calendriers ne contiennent pas d'événements, ce n'est qu'un attribut d'un événement

Pour plus de détails sur ces classes, voir la section du site developer.apple.com.

EventKit à un petit frère qui s'appelle EventKitUI et qui vous fournit des vues et controlleurs standards pour pouvoir faire du CRUD d'un événement. On ne parlera pas de ce framework ici car il n'a pas été utilisé sur ce projet mais on reviendra plus loin sur ses manques. Pour plus de détails, voir ici.

Cas d'usages simples

  • Récupérer la liste des calendriers

Tout se passe via la classe EKEventStore :

EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease];
NSArray *calendars = [eventStore calendars];

Les propriétés d'intérêt sur EKCalendar sont ensuite son nom (title) et la possibilité d'accès en écriture à ce calendrier (allowsContentModifications).

  • Connaître le calendrier par défaut

Si vous souhaitez écrire dans les calendriers de l'utilisateur, il vous faudra donner le choix du calendrier à l'utilisateur. Pour cela un bon défaut est celui que l'utilisateur a choisi comme "calendrier par défaut" sur son iPhone. Vous pouvez le récupérer via la propriété defaultCalendarForNewEvents: :

EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease];
EKCalendar *defaultCalendar = [eventStore defaultCalendarForNewEvents];
  • Rechercher un événement dans un calendrier

La recherche passe par la classe EKEventStore. La recherche se fait soit via un prédicat sur des propriétés de l'événement soit par une recherche via l'identifiant de l'événement. Un point important est que l'identifiant est unique et que vous pouvez donc l'utiliser quelque soit l'instance de l'EKEventStore.

Voilà comment se fait une recherche via un prédicat dans tous les calendriers :

EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease];
NSDate startDate = [...];
NSDate endDate = [...];
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil]; // nil = all calendars
NSArray *matchingEvents = [eventStore eventsMatchingPredicate: predicate];
  • Ajouter un événement dans un calendrier

Là encore tout passe par la classe EKEventStore :

EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease];

EKEvent *event = [EKEvent eventWithEventStore:eventStore];

event.title = @"BGPI - Événement de test";
event.startDate = [NSDate date];
event.endDate = [NSDate date];
	
event.allDay = YES;
event.availability = EKEventAvailabilityFree;	
event.notes = @"Événement créé par l'application \'Mon agenda patrimonial\'";

if( ![eventStore saveEvent:event 			  span:EKSpanThisEvent 			 error:&error] ) { 	     NSString *message = [[[NSString alloc] initWithFormat:@"Error adding event %@ in calendar %@: %@", [event title], [defaultCalendar title], [error localizedDescription]] autorelease];      UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Erreur" message:message delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil] autorelease];      [alert show];		 } ```  En plus des champs présentés dans le code ci-dessous, vous pouvez rajouter des participants, des alarmes et une récurrence (se référer aux classes _EKParticipant_, _EKAlarm_ et _EKRecurrenceRule_).     - Supprimer un événement  La suppression d'un événement se fait via la méthode _removeEvent:span:error:_ de _EKEventStore_. Il vous faudra par contre avoir une référence de l'événement dans ce store. C'est une subtilité importante, il faut que cet événement ait été **récupéré dans le même store**, soit par une recherche sur des caractéristiques de l'événement soit par une recherche par son identifiant.  ``` EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease]; NSError *error; 	 EKEvent *event = [...];  if( ![eventStore removeEvent:event span:EKSpanThisEvent error:&error]) { 	NSLog(@"Error deleting event %@ in calendar: %@",  		  [event title],  		  [event.calendar title], 		  [error localizedDescription]); } ```  Le paramètre _span_ est utile dans le cas d'un événement récurrent.     - Vérifier que EventKit est disponible sur le device  Vous avez une application dont la fonctionnalité principale n'est pas reliée à l'agenda ? Vous pouvez alors choisir de ne pas restreindre vos utilisateurs aux seuls personnes ayant installé iOS4 (même si cela représente déjà plus de 90% du parc).  Voilà comment faire :  - Premièremement au niveau du projet, il vous faut linker de manière "faible" (_weak_) avec le framework EventKit sous XCode. Tout se passe dans les informations de votre target de build : ![Weak link EventKit in XCode](xcode-weak-link-eventkit.png)- Deuxièmement dans votre code, il vous faudra vérifier qu'Eventkit est bien présent au runtime, par exemple en testant qu'une des classes d'EventKit est bien présente :

if( !NSClassFromString(@"EKEventStore") ){ // do stuff with event kit }


Notez que vous pouvez également faire ça en testant la version de l'OS via la méthode _systemVersion:_ de la classe _UIDevice_.

Un bon point de départ sur cette problématique de compatibilité est [cet article](http://iphonedevelopertips.com/xcode/base-sdk-and-iphone-os-deployment-target-developing-apps-with-the-4-x-sdk-deploying-to-3-x-devices.html).

Voilà pour ce qui est des usages simples autour du calendrier. Parlons maintenant un peu des avantages / inconvénients de ce framework et de notre retour d'expérience sur ce dernier.

## Love...

Clairement, un des gros points fort est que l'**API est très simple**.

Le développeur accède de plus à tous les calendriers **indépendamment du provider** du calendrier (Google Calendar, Exchange, Lotus, le calendrier de l'iPhone en local etc...) **et du protocole d'accès** (CalDAV, propriétaire etc...).

Un autre point fort du SDK est que les calendriers distants sont "cachés" localement et que donc **l'accès à un calendrier distant offline est transparent** pour le développeur, il n'a pas besoin de se soucier de la connectivité.

Dis comme ça ces points peuvent paraître anodins mais ce sont des avantages extrêmement forts pour le développeur. Comprenez par là que même si je m'étends moins longuement sur cette section que sur la suivante, je suis pour autant **convaincu de la valeur d'EventKit pour le développeur**.

## and dislike...

Une des forces de l'API est sa simplicité. Simple ne veut pas dire limité mais dans le cas d'EventKit, c'est malheureusement un peu le cas.

### Une API limitée

Cela s'est concrétisé dans mon expérience sur deux volets :

- Sur la recherche d'événements
A priori super standard, la recherche ne peut se faire que sur une plage de dates ou sur un sous-ensemble des calendriers. C'est très vite limitant et vous vous retrouvez alors à devoir potentiellement **réimplémenter la recherche** dans votre code. Ce qui est surprenant c'est qu'Apple a utilisé le méchanisme standard NSPredicate pour la recherche mais a bridé les propriétés utilisables à la création du predicate.

Vous vous retrouvez donc à faire des choses du type :

[...] // First filter on date if possible NSPredicate *limitedPredicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil]; NSArray *partiallyMatchingEvents = [eventStore eventsMatchingPredicate: limitedPredicate];

// Then filter on properties NSPredicate *fullPredicate = [NSPredicate predicateWithFormat:@"title == %@", @"Titre à rechercher"]; NSArray *matchingEvents = [partiallyMatchingEvents filterUsingPredicate:fullPredicate];


Ce qui est tout de même malheureux et surtout **handicapant d'un point de vue performances**. Dans notre cas, on ne pouvait pas se permettre par exemple à chaque mise à jour de rechercher sur toute l'année en cours dans le calendrier et il a donc été décidé de persister les identifiants des événements afin de pouvoir y accèder rapidement (via la méthode _eventWithIdentifier:_).  
- Sur le choix d'un calendrier par l'utilisateur

J'en parlais au début, le framework EventKitUI est pour moi un peu léger et c'est tout de même malheureux de **devoir refaire l'écran de choix du calendrier** pourtant déjà disponible dans l'application Apple _Calendrier_ ! A quand son inclusion dans EventKitUI ?

![UIView Calendar Picker](calendar-picker.png)

### Des soucis de fiabilité et de performance ?

Dernier point et non des moindres, on a rencontré des soucis de fiabilité, certainement dûs à **un framework un peu jeune ?** Dans notre cas d'usage, c'est-à-dire l'insertion en batch d'événements dans un calendrier, il se produisait dans une fraction des cas des **erreurs silencieuses**. Concrètement, en insérant de manière séquentielle une cinquantaine d'événements dans le calendrier, il manquait dans la plupart des cas, et sans erreur retournée par l'API (_saveEvent:span:error:_), un événement sur les cinquante. Un bug a été ouvert chez Apple à ce sujet (_Bug ID# 9405210_).

Ce problème a été contourné en vérifiant après insertion que l'événement était effectivement dans le calendrier via la méthode _refresh:_ mais cet appel a un coût en termes de performances.

En parlant de performance, il se trouve que **le framework est un peu lent**. Quelques temps très inexacts, expérimentés sur un iPhone 3G en iOS 4.2.1 :

  

|     | Sans vérification | Avec vérification |
| --- | --- | --- |
| Temps d'insertion de 50 événements | 20 s | 23 s |

Là encore mon analyse est que **l'API a été trop simplifiée**. Par exemple il **manque clairement une méthode d'insertion en batch** (un _saveEvents:_ !), notre cas d'usage étant très certainement pénalisé par des locks pour accèder au calendrier, autant de fois qu'il y a d'événements à insérer.

Pensez donc, selon votre cas d'usage, à déléguer l'insertion des événements à un autre thread que la thread principale afin de ne pas bloquer l'interface (par exemple via la méthode _detachNewThreadSelector:_ de la classe _NSThread_).

  

J'espère que cet article vous aura donné un bon aperçu du framework EventKit et quelques retours concrets sur le framework. Les cas d'usages sont nombreux, que ce soit par exemple pour des applications administratives (rappel d'échéances, rappel d'un rendez-vous avec votre banquier...), ou des applications de productivité personnelle.

**Et vous, quel est votre REX sur ce framework ?**