How to get all events from an EKCalendar in iOS - ios

I've created a calendar with some events, now I want to get all events from that calendar, without knowing what their start or end date is.
Code I tried, getting all events from all calendars, so I could divide them by calendar ID later or so.. :
NSPredicate *fetchCalendarEvents = [eventStore predicateForEventsWithStartDate:[NSDate distantPast] endDate:[NSDate distantFuture] calendars:eventStore.calendars];
NSArray *allEvents = [eventStore eventsMatchingPredicate:fetchCalendarEvents];
This makes allEvents return nil while the database clearly shows events in the calendar.
Anyone could help me?

I have it working now.
This is the code I'm using:
NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow:[[NSDate distantFuture] timeIntervalSinceReferenceDate]];
NSArray *calendarArray = [NSArray arrayWithObject:cal];
NSPredicate *fetchCalendarEvents = [eventStore predicateForEventsWithStartDate:[NSDate date] endDate:endDate calendars:calendarArray];
NSArray *eventList = [eventStore eventsMatchingPredicate:fetchCalendarEvents];
for(int i=0; i < eventList.count; i++){
NSLog(#"Event Title:%#", [[eventList objectAtIndex:i] title]);
}
Gives me all events from the calendar I'm using for my app from the date it is right when you call the method.
I think giving [NSDate distantPast] won't work because it's in the past or something.. setting your startDate as [NSDate date] will work.
Hope this helps people who have the same problem..

If you would like to lookup a year span greater than 4 years, the returned events would be trimmed by the system to those from the first 4 years. (see Apple's documentation). What could be a possible solution is to run the predicate matching in batches.
Here's a C# extension for Xamarin.iOS
public static class EKEventStoreExtensions
{
private const int MaxPredicateYearSpan = 4;
public static EKEvent[] GetAllEvents(this EKEventStore eventStore, DateTimeOffset startAt, DateTimeOffset endAt, params EKCalendar[] calendars)
{
var isBatched = endAt.Year - startAt.Year >= MaxPredicateYearSpan;
var result = new List<EKEvent>();
var batchStartAt = startAt;
var batchEndAt = endAt;
while (batchStartAt < endAt)
{
if (isBatched)
{
batchEndAt = batchStartAt.AddYears(1);
if (batchEndAt > endAt)
{
batchEndAt = endAt;
}
}
var events = GetEventsMatching(eventStore, batchStartAt, batchEndAt, calendars);
result.AddRange(events);
batchStartAt = batchEndAt;
}
return result.ToArray();
}
private static EKEvent[] GetEventsMatching(EKEventStore eventStore, DateTimeOffset startAt, DateTimeOffset endAt, EKCalendar[] calendars)
{
var startDate = (NSDate)startAt.LocalDateTime;
var endDate = (NSDate)endAt.LocalDateTime;
var fetchCalendarEvents = eventStore.PredicateForEvents(startDate, endDate, calendars);
return eventStore.EventsMatching(fetchCalendarEvents);
}
}

Related

iOS: predicateForEventsWithStartDate does not return events starting outside of search frame

Simple example I use with the code in the snippet below:
Search frame: 15 Apr 2016 15:00pm-18:00pm
Events which are entered in the calendar:
Event 1: 15 Apr 2016 12:00pm-16:00pm
Event 2: 15 Apr 2016 17:00pm-20:00pm
Now the used method returns me only one object - that is the Event 2. Apparently I hope to get returned both - Event 1 and 2.
I really don't understand why this happens! What do I miss in my code? Thanks in advance.
int seconds_in_year = 60*60*24*365;
__block
NSDate* currentStart = [NSDate dateWithTimeInterval:0 sinceDate:startTime];
__block
NSDate* currentFinish = [NSDate dateWithTimeInterval:seconds_in_year sinceDate:currentStart];
// use Dictionary for remove duplicates produced by events covered more one year segment
NSMutableDictionary *eventsDict = [NSMutableDictionary dictionaryWithCapacity:1024];
EKEventStore *store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (!granted) { return; }
// enumerate events by one year segment because iOS do not support predicate longer than 4 year !
while ([currentStart compare:endTime] == NSOrderedAscending) {
if ([currentFinish compare:endTime] == NSOrderedDescending) {
currentFinish = [NSDate dateWithTimeInterval:0 sinceDate:endTime];
}
NSPredicate *predicate = [store predicateForEventsWithStartDate:currentStart endDate:currentFinish calendars:nil];
[store enumerateEventsMatchingPredicate:predicate
usingBlock:^(EKEvent *event, BOOL *stop) {
if (event) {
[eventsDict setObject:event forKey:event.eventIdentifier];
}
}];
currentStart = [NSDate dateWithTimeInterval:(seconds_in_year + 1) sinceDate:currentStart];
}
return;
}];
EDIT:
If I try another Event 3 (15 Apr 2016 14:00pm-19:00pm) then the method returns me the whole search period as to be an "event" in the calendar. That is correct compared to the previous situation which still causes me some headaches.
As the method name implies predicateForEventsWithStartDate searches for startDates which are equal to or later than the passed date argument.

Check if Event exists on Calendar

I'm having trouble verifying if an event already exists on the user's calendar. I need to check this to determine if I should add it or not, so that I don't create duplicate calendar entries. Right now, I create a duplicate entry every time I run the code.
First, here is how I am creating the calendar entry:
+ (NSString *) addEventToCalenderWithDate : (NSDate *) eventDate
eventTitle : (NSString *) eventTitle
eventLocation : (NSString *) eventLocation
allDayEvent : (BOOL) isAllDay
{
EKEventStore *store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (!granted) {
returnValue = #"calendar error";
}
else if ([self eventExists:dateAndTime eventTitle:eventTitle allDayEvent:isAllDay]) {
returnValue = #"duplicate";
}
else {
EKEvent *event = [EKEvent eventWithEventStore:store];
event.title = eventTitle;
event.startDate = dateAndTime;
if (eventTimeString == (id)[NSNull null] || eventTimeString.length == 0 || isAllDay) {
event.allDay = YES;
event.endDate = dateAndTime;
} else {
event.endDate = [event.startDate dateByAddingTimeInterval:60*60]; //set 1 hour meeting
}
event.location = eventLocation;
[event setCalendar:[store defaultCalendarForNewEvents]];
NSError *err = nil;
[store saveEvent:event span:EKSpanThisEvent commit:YES error:&err];
returnValue = #"success";
}
}];
return returnValue;
}
This sets the event correctly. However, if I run it again, I expect that the else if clause will return YES and no new entry will be created. However, it always returns NO and I create a new calendar entry with each execution. Here is that method:
+ (BOOL) eventExists : (NSDate *) date
eventTitle : (NSString *) eventTitle
allDayEvent : (BOOL) isAllDay
{
EKEventStore *store = [[EKEventStore alloc] init];
NSPredicate *predicateForEventOnDate = [[NSPredicate alloc] init];
if (isAllDay)
predicateForEventOnDate = [store predicateForEventsWithStartDate:date endDate:date calendars:nil]; // nil will search through all calendars
else
predicateForEventOnDate = [store predicateForEventsWithStartDate:date endDate:[date dateByAddingTimeInterval:60*60] calendars:nil]; // nil will search through all calendars
NSArray *eventOnDate = [store eventsMatchingPredicate:predicateForEventOnDate];
NSLog(#"eventOnDate: %#", eventOnDate);
BOOL eventExists = NO;
for (EKEvent *eventToCheck in eventOnDate) {
if ([eventToCheck.title isEqualToString:eventTitle]) {
eventExists = YES;
}
}
return eventExists;
}
As I step through this method, I notice that the NSArray called eventOnDate is nil (the EKEventStore is not nil). I don't know if this means that it simply did not find any matching events or if something else is going on.
What am I doing wrong that won't allow this to identify existing events on the calendar? Thank you!
The problem appears to be with the date range you have selected for your predicate.
predicateForEventOnDate = [store predicateForEventsWithStartDate:date endDate:date calendars:nil];
This will look for events within a "0" second range because the start and end date of your predicate query is identical.
predicateForEventOnDate = [store predicateForEventsWithStartDate:date endDate:[date dateByAddingTimeInterval:60*60] calendars:nil];
This will only look for events that lie within an hour of the date provided.
NSCalendar *const calendar = NSCalendar.currentCalendar;
NSCalendarUnit const preservedComponents = (NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay);
//strip away hours, minutes and seconds to find date - at start of day
NSDateComponents *startComponents = [calendar components:preservedComponents fromDate:self.date];
//set finished date to 1 full day later
NSDateComponents *offset = [[NSDateComponents alloc] init];
[offset setDay:1];
NSDate *start = [calendar dateFromComponents:startComponents];
NSDate *finish = [calendar dateByAddingComponents:offset toDate:self.date options:0];
NSPredicate *predicateForEventOnDate = [[NSPredicate alloc] init];
if (isAllDay)
predicateForEventOnDate = [store predicateForEventsWithStartDate:start endDate:finish calendars:nil];
NSArray *eventOnDate = [store eventsMatchingPredicate:predicateForEventOnDate];
This will produce an array that covers events for the full day from start to finish.

EventKit last event of calendar

I'm making an app in which I sync certain events to a calendar on iPhone.
The problem is, I have no way of telling which events were altered/removed/...
So I need to remove all the events between today and the end date of the last event of the calendar before 'syncing' (read inserting) the new events.
As far as I've seen, the only way to do an action on multiple events at once, is by using enumerateEventsMatchingPredicate:usingBlock: and predicateForEventsWithStartDate:endDate:calendars:
But for this I need a specific end date. (and thus, the end date of the last event in my calendar)
I could always save the event identifier of the last event I insert into this calendar, but I would rather not do this:
If the user uninstalls my app and installs it again later, I don't have the last event identifier anymore. (given that (s)he didn't remove the calendar manually of course)
I could just remove the calendar every time I need to sync the calendar, but then I would lose all passed events.
Any ideas or tips are much appreciated!
For your comment on :
I can't seem to find any way to fetch all the events of a calendar.
Actually you can fetch all the events from calendar :
NSDate *start = ...
NSDate *finish = ...
// use Dictionary for remove duplicates produced by events covered more one year segment
NSMutableDictionary *eventsDict = [NSMutableDictionary dictionaryWithCapacity:1024];
NSDate* currentStart = [NSDate dateWithTimeInterval:0 sinceDate:start];
int seconds_in_year = 60*60*24*365;
// enumerate events by one year segment because iOS do not support predicate longer than 4 year !
while ([currentStart compare:finish] == NSOrderedAscending) {
NSDate* currentFinish = [NSDate dateWithTimeInterval:seconds_in_year sinceDate:currentStart];
if ([currentFinish compare:finish] == NSOrderedDescending) {
currentFinish = [NSDate dateWithTimeInterval:0 sinceDate:finish];
}
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:currentStart endDate:currentFinish calendars:nil];
[eventStore enumerateEventsMatchingPredicate:predicate
usingBlock:^(EKEvent *event, BOOL *stop) {
if (event) {
[eventsDict setObject:event forKey:event.eventIdentifier];
}
}];
currentStart = [NSDate dateWithTimeInterval:(seconds_in_year + 1) sinceDate:currentStart];
}
NSArray *events = [eventsDict allValues];

EventKit - multiple methods named 'location'

I am trying to list out the Name, Location, and Notes from calendar events. Reading and writing the Name and Notes work as expected but I run into problems with the Location field.
Specifically, the following line "meetingLocation = [element location];" produces the error
"Multiple methods named 'location' found with mismatched result, parameter type or attributes."
What is wrong here? Code is included below.
-(IBAction)reloadEvents:(id)sender {
NSString *meetingName;
NSString *meetingLocation;
NSString *meetingNotes;
// Define a range of event dates we want to display
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:(-1*60*60*.5)]; // .5 hour in the past
NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:(60*60*24*1)]; // 1 day from now
//NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:(60*60*24*7)]; // 7 days from now
// Create a predicate to search all celndars with our date range using the start date/time of the event
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil];
// Query the event store using the predicate.
NSArray *results = [self.eventStore eventsMatchingPredicate:predicate];
// Convert the results to a mutable array and store so we can implement swipe to delete
//NSMutableArray *events = [[NSMutableArray alloc] initWithArray:results];
//self.events = events;
NSEnumerator * enumerator = [results objectEnumerator];
id element;
while(element = [enumerator nextObject])
{
// Set the meeting name
meetingName = [element title];
NSLog(#"Name=%#",meetingName);
// Set the meeting location
meetingLocation = [element location];
NSLog(#"Location=%#",meetingLocation);
// Set the meeting notes
meetingNotes = [element notes];
NSLog(#"Notes=%#",meetingNotes);
}
}
Try like this
while(element = [enumerator nextObject])
{
EKEvent *event = element;
meetingName = event.location;
}
Similar problem converting some old iOS 5 to iOS7:
if ([[thisEvent.recurrenceRules objectAtIndex:i] frequency] == EKRecurrenceFrequencyDaily ){
resulted in
Multiple methods names "frequency" found with mismatched result.
Resolved by typecasting and then doing the if statement
EKRecurrenceRule *thisRecurranceRule = (EKRecurrenceRule *)[thisEvent.recurrenceRules objectAtIndex:i] ;
if ([thisRecurranceRule frequency] == EKRecurrenceFrequencyDaily ){

How to detect which EKevent was changed

I got the problem. I need to know when Events in my EventStore are changed, so for this case I use EKEventStoreChangedNotification but this notification return to me incomprehensible dictionary in userInfo
It's look like this:
EKEventStoreChangedObjectIDsUserInfoKey = ("x-apple-eventkit:///Event/p429" );
I don't know how I can use this data to taking access for changed object. Please help me
This will detect changed events and log the event titles over a date range. Although, I ended up not doing this because in practice I don't know the date range. I need to compare with all the events I'm working with, which means I need to refresh them anyway since the object IDs might have changed. This ends up making each event not so useful and now I just refresh every few seconds when changes come in and ignore the details. I hope Apple improves these notifications.
#pragma mark - Calendar Changed
- (void)calendarChanged:(NSNotification *)notification {
EKEventStore *ekEventStore = notification.object;
NSDate *now = [NSDate date];
NSDateComponents *offsetComponents = [NSDateComponents new];
[offsetComponents setDay:0];
[offsetComponents setMonth:4];
[offsetComponents setYear:0];
NSDate *endDate = [[NSCalendar currentCalendar] dateByAddingComponents:offsetComponents toDate:now options:0];
NSArray *ekEventStoreChangedObjectIDArray = [notification.userInfo objectForKey:#"EKEventStoreChangedObjectIDsUserInfoKey"];
NSPredicate *predicate = [ekEventStore predicateForEventsWithStartDate:now
endDate:endDate
calendars:nil];
// Loop through all events in range
[ekEventStore enumerateEventsMatchingPredicate:predicate usingBlock:^(EKEvent *ekEvent, BOOL *stop) {
// Check this event against each ekObjectID in notification
[ekEventStoreChangedObjectIDArray enumerateObjectsUsingBlock:^(NSString *ekEventStoreChangedObjectID, NSUInteger idx, BOOL *stop) {
NSObject *ekObjectID = [(NSManagedObject *)ekEvent objectID];
if ([ekEventStoreChangedObjectID isEqual:ekObjectID]) {
// Log the event we found and stop (each event should only exist once in store)
NSLog(#"calendarChanged(): Event Changed: title:%#", ekEvent.title);
*stop = YES;
}
}];
}];
}

Resources