How to Use the Xuni iOS Calendar with EventKit
The new calendar control in the 2016v1 release of Xuni enables a new way of visualizing time related data for iOS users. A major point of interest is using the Xuni calendar control with the calendar data stored on your iOS device. Apple provides an API for accessing this built-in calendar data (such as events and reminders) called EventKit. In this article we'll examine both how to populate the calendar control with local data, as well as how to save new events using EventKit.
EventKit
The EventKit framework allows users to interact with an iOS device's calendar database. The calendar database stores not only calendar events, but also reminders. We'll be using this API both to read and write some event data to the calendar database. Apple provides some in-depth documentation that can be found here. There can be multiple calendars in the calendar database. In this article, we'll mostly be dealing with the default calendar and not anything more specific, but it's possible to both choose from other available calendars or create create a new one one specific to your application. Since the Xuni calendar control is a merely a way to visualize the calendar data, it makes sense that to use it with EventKit. We recently published an in-depth look at the new features in the control which should provide further context for what customizations are possible.
The calendar sample specs
The UI of the calendar sample is going fill a few requirements:
- Display a a month view of calendar data noting the days with events
- Provide a table below with all of the events for a selected day
- Provide a form for adding new events
The resulting application will have two ViewControllers: o ViewController with a month view (the calendar control) and a daily view (a UITableView with any events for that day) and another ViewController that allows you to enter a new event. Main ViewController Add Event ViewController
Getting access and fetching data
EventKit allows you to retrieve the calendar database information and place it into an EKEventStore object. Accessing the calendar database requires that the user grants the app permission. With that in mind, we need an EKEventStore object and a bool to track whether the application has access.
EKEventStore *eventStore;
bool isAccessGranted;
When the app starts, we'll call a method to ask for the user's permission to access their calendar. This requires checking each possible EKAuthorization status, prompting for permission if the status isn't determined, and displaying a message if access has been denied.
- (void)updateAuthorizationStatusToAccessEventStore {
EKAuthorizationStatus authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
switch (authorizationStatus) {
case EKAuthorizationStatusDenied:
case EKAuthorizationStatusRestricted: {
isAccessGranted = NO;
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Access Denied"
message:@"App needs permission to access calendar events"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
[tv reloadData];
break;
}
case EKAuthorizationStatusAuthorized:
isAccessGranted = YES;
[tv reloadData];
break;
case EKAuthorizationStatusNotDetermined: {
[eventStore requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error) {
dispatch\_async(dispatch\_get\_main\_queue(), ^{
isAccessGranted = granted;
[tv reloadData];
});
}];
break;
}
}
}
Assuming the user grants access, the next step is to fetch data to populate the various UI components. Fetching events is relatively straightforward. Using a NSPredicate with a start and end date, we'll specify how the event data is fetched from the EKEventStore. We'll pull in events from the calendar database and place them into an array.
- (void)fetchEventsWithStart:(NSDate *)start end:(NSDate *) end{
if (isAccessGranted) {
NSPredicate *predicate =
[eventStore predicateForEventsWithStartDate:start endDate:end calendars:nil];
eventsArray = [eventStore eventsMatchingPredicate:predicate];
}
}
Now that we can fetch data, we can focus on populating the individual controls.
Populating the calendar
The Xuni calendar control will have a small marker on any day where an event occurs. We'll need to create our own implementation of the daySlotLoading method which is a part of the XuniCalendarDelegate protocol. This method will fire for each calendar day and allow us to customize that date. For each date we'll need to call the fetchEvent method that we created above to check and see if any events occur that day. If the eventArray object has any events in it, we'll draw the marker on that daySlot.
-(void)daySlotLoading:(XuniCalendar *)sender args:(XuniCalendarDaySlotLoadingEventArgs *)args{
NSDate *start;
NSDate *end;
NSCalendar *cal = [NSCalendar currentCalendar];
NSCalendarUnit preservedComponent = (NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay);
NSDateComponents *components = [cal components:preservedComponent fromDate:args.date];
NSDateComponents *oneDayPlus = [[NSDateComponents alloc] init];
oneDayPlus.day = 1;
start = [cal dateFromComponents:components];
components = [cal components:preservedComponent fromDate:args.date];
end = [cal dateByAddingComponents:oneDayPlus toDate:[cal dateFromComponents:components] options:0];
[self fetchEventsWithStart:start end:end];
if(eventsArray){
CGRect rect = args.daySlot.frame;
CGSize size = rect.size;
CGRect rect1, rect2;
XuniCalendarImageDaySlot *imageDaySlot = [[XuniCalendarImageDaySlot alloc] initWithCalendar:sender frame:rect];
rect1 = CGRectMake(0, 0, size.width, size.height / 6 * 4);
rect2 = CGRectMake(size.width / 2 - 4, size.height / 6 * 4, 8, 8);
imageDaySlot.dayTextRect = rect1;
imageDaySlot.imageRect = rect2;
imageDaySlot.imageSource = [UIImage imageNamed:@"event"];
args.daySlot = imageDaySlot;
}
}
Populating the table
Populating the UITableView is also straightforward. The table will always be filled with all of the events that are currently in the eventsArray. We'll place the title, start, and end times of the individual events into each table cell. The dates need to be formatted and appended together before they're placed into the cell detail.
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *SimpleIdentifier = @"SimpleIdentifier";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:SimpleIdentifier];
EKEvent *event = [eventsArray objectAtIndex:indexPath.row];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SimpleIdentifier];
}
cell.textLabel.text = event.title;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"h:mm a"];
cell.detailTextLabel.text = [[[dateFormat stringFromDate: event.startDate] stringByAppendingString:@" - "]stringByAppendingString:[dateFormat stringFromDate: event.endDate]];
return cell;
}
Now, we need to tie the table and the calendar control together based on the currently selected date in the calendar. We can use the selectionChanged event to determine when this occurs, and then fetch the events that occur on that selected day. Once the data has been fetched and placed into the eventsArray, we'll reload the tableview to populate it with all of the event data.
-(void)selectionChanged:(XuniCalendar *)sender args:(XuniCalendarSelectionChangedEventArgs *)args{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"LLLL dd yyyy"];
selectedDate = [dateFormat stringFromDate:args.selectedDates.startDate];
NSCalendar *cal = [NSCalendar currentCalendar];
NSCalendarUnit preservedComponent = (NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay);
NSDateComponents *components = [cal components:preservedComponent fromDate:args.selectedDates.startDate];
NSDateComponents *oneDayPlus = [[NSDateComponents alloc] init];
oneDayPlus.day = 1;
sdate = [cal dateFromComponents:components];
components = [cal components:preservedComponent fromDate:args.selectedDates.endDate];
edate = [cal dateByAddingComponents:oneDayPlus toDate:[cal dateFromComponents:components] options:0];
dispatch\_async(dispatch\_get\_main\_queue(), ^{
[self fetchEventsWithStart:sdate end:edate];
[tv reloadData];
});
}
Adding new events
The second ViewController is an entry form for handling adding new events. Rather than cover every design detail, we'll skip to the most interesting part where we actually add the event to the calendar database. Adding a new EKEvent is a pretty easy process. Basically you can just create a new event object, populate a few properties, and then save the event to your EKEventStore. In this case we're also saving it to the defaultCalendarForNewEvents, though you could use your own calendar too.
-(void)addClicked{
EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
event.title = eventField.text;
event.startDate = self.startTime;
event.endDate = self.endTime;
[event setCalendar:[self.eventStore defaultCalendarForNewEvents]];
NSError *err;
[self.eventStore saveEvent:event span:EKSpanThisEvent commit:true error:&err];
[self.navigationController popViewControllerAnimated:true];
}
Conclusion
This is only a starting point for what's possible using the EventKit with the Xuni calendar control. EventKit is also a mechanism for interacting with reminders and alarms, and there are further options for customizing your events outside the scope of this article. The calendar control makes visualizing this data in your app much easier. The full sample is available if you want to take a closer look at the implementation.