I'm trying to get all the events from a determined EKCalendar, but if I call eventsMatching Predicate: with a predicate generated with predicateForEventsWithStartDate:endDate:calendars: and a past 'startDate' I get a null object. It's a bug? Or there is no way I can retrive the past events?
EDIT (NSData decalarations):
[[self eventStore] predicateForEventsWithStartDate:[NSDate distantPast] endDate:[NSDate distantFuture] calendars:syncedCalendars]
Try change your code to to:
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:[[NSDate distantFuture] timeIntervalSinceReferenceDate]];
[[self eventStore] predicateForEventsWithStartDate:[NSDate date] endDate: futureDate calendars:syncedCalendars]
Also make sure that your syncedCalendars is NSArray type.
Related
I fetch an array of events from Core Data. In turn, I want to filter the results to obtain any events currently taking place, ie that started within the last hour.
I'm a bit fuzzy on how to filter the array and even fuzzier on how to work with dates in an NSpredicate. I think I need to calculate a time that is one hour before the present, a second that is a short time from the futrue, and then compare these the starttime but I'm not sure how to go about it.
This is what I have so far:
//fetch from coredata
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest
error:&error];
NSMutableArray *mutableresults = [results mutableCopy];
NSDate* now = [NSDate date];
NSTimeInterval backSecondsInHour = -60 * 60;
NSTimeInterval forwardSecondsInHour = 60 * 60;
NSDate* hourAgo = [NSDate dateWithTimeInterval:backSecondsInHour sinceDate:now];
NSDate* hourFromNow = [NSDate dateWithTimeInterval:forwardSecondsInHour sinceDate:now];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"starttime >= %#&&starttime<=%#",hourAgo,hourFromNow];
NSArray *filteredEvents=[mutableresults filteredArrayUsingPredicate:predicate];
Am I on the right track here? Would appreciate any suggestions.
Instead retrieving all events every time from database just fetch only needed events...
Here is example if predicate applied to fetchRequest for all events since last hour:
NSDate *hourAgo = [[Date date] dateByAddingTimeInterval:-3600];
NSFetchRequest *fetchRequest = .....
request.predicate = [NSPredicate predicateWithFormat:"starttime >= %#", hourAgo];
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];
I have an NSMutableArray called self.objectArray, that contains custom objects. Each object holds an NSDictionary and two other string objects. Actually I need to work only with the dictionary. Every dictionary contains a key named keyDate which holds an NSString that look like this: MM/dd/yy HH:mm:ss.
I would like to sort the array based on their keyDate. The object with the oldest date should be the first object and so on. I've found some questions, that looked helpful and I could create the code that you can see below, but I get an error everytime I run it. As I think NSSortDescriptor won't be the right tool since my keys aren't key value compliant.
PNMessage 0x125c0590> valueForUndefinedKey:]: this class is not key value coding-compliant for the key keyDate.'
NSSortDescriptor *dateDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"keyDate"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:dateDescriptor];
NSArray *sortedEventArray = [self.objectArray
sortedArrayUsingDescriptors:sortDescriptors];
self.finallySorted = [sortedEventArray mutableCopy];
If it's possible I would do it with sort descriptor, however I think there should be some other options, but can't figure out its proper implementation.
So I can also catch every object's keyDate with a for loop, but don't know how can I sort them based on the value. I would really appreciate if somebody could show me the right way.
for(PNMessage *mg in self.objectArray)
{
NSLog(#" test log %#", mg.message[#"keyDate"]);
}
I already checked this answer:
How to sort an NSMutableArray with custom objects in it?
but the structure of my object is different.
My first code based on this question, but it doesn't worked.
How to sort an NSMutableArray with custom objects in it?
UPDATE: my try based on Kaan's answer (doesn't works yet)
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM/dd/yy HH:mm:ss"];
}
NSArray *sortedArray = [self.object sortedArrayUsingComparator:^NSComparisonResult(PNMessage *obj1, PNMessage *obj2) {
NSString *date1String = obj1.message[#"keyDate"];
NSString *date2String = obj1.message[#"keyDate"];
NSDate *date1 = [formatter dateFromString:date1String];
NSDate *date2 = [formatter dateFromString:date2String];
if ( date1 < date2 ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( date1 > date2 ) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
I would consider using the sortedArrayUsingComparator method
Assuming your custom class is called PNMessage:
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setFormat:#"MM/dd/yy HH:mm:ss"];
}
NSArray *sortedArray = [self.objectArray sortedArrayUsingComparator:^NSComparisonResult(PNMessage *obj1, PNMessage *obj2) {
NSString *date1String = obj1[#"keyDate"];
NSString *date2String = obj1[#"keyDate"];
NSDate *date1 = [formatter dateFromString:date1String];
NSDate *date2 = [formatter dateFromString:date2String];
return [date1 compare:date2];
}];
Tip: If you decide on following this, make sure you declare your NSDateFormatter instance as static outside of the sorting body, since allocating Formatters in iOS can be very expensive and cause serious performance penalties.
I am listing the events in my app. User can create, edit and delete the events. In viewDidLoad method I fetch all events I need and push them into an array. It works like expected.
For creating, editing and deleting events I use EKEventEditViewController and EKEventViewController which works pretty well. In delegate methods of the controllers I make the changes I need on my array and reload my view.
Of course I would like also know and handle, if user make some changes from another app (like built-in calendar app). So I observe EKEventStoreChangedNotification. From that notification I get only "changes have been occurred" and not which event or from which app. Actually what I want to know is, if the change has been occurred from my app or another app and which events have been changed. Since I already handle the changes(from my app) in EKEventEditViewControllerDelegate method, I do not need to handle them again.
If I do not know which objects have been changed, I have to fetch ans sort all of them.
For now I have only 5 events in the calendar(development device), of course it is not a problem to fetch and sort all events, but if user has more then 1000, it is overkill for maybe only one event change.
So my question is: How to handle EKEventStoreChangedNotification?
You can detect exactly which event has been changed by the following code [Disclaimer code is not my idea, I have found it in another Stack Overflow answer and modified it a little bit].
I'm using a lib called "JSCalendarManager" for interaction with eventstore and in my case as the events created using my App and synced with iCalendar I already saved their eventIdentifier in local DB , I can retrieve my time bound to search for events in iCalendar and get match for changed one.
+(void)iCloudStoreChanged:(NSNotification*)eventStoreChangeNotification{
NSArray* allScheduleRecords =[self getAllScheduleRecordSyncedToICalendar];
NSDate* startDate = [NSDate new];
NSDate* endDate = [NSDate new];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
if (allScheduleRecords.count >= 2) {
startDate = [dateFormatter dateFromString:[[allScheduleRecords firstObject] objectForKey:#"meetingTime"]];
endDate = [dateFormatter dateFromString:[[allScheduleRecords lastObject] objectForKey:#"meetingTime"]];
}else if (allScheduleRecords.count > 0){
startDate = [dateFormatter dateFromString:[[allScheduleRecords firstObject] objectForKey:#"meetingTime"]];
NSDate *today = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:(NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:today];
components.day = 1;
endDate = [gregorian dateFromComponents:components];
}else{
}
NSArray *ekEventStoreChangedObjectIDArray = [eventStoreChangeNotification.userInfo objectForKey:#"EKEventStoreChangedObjectIDsUserInfoKey"];
[calendarManager findEventsBetween:startDate
and:endDate
withSearchHandler:^(BOOL found, NSError *error, NSArray *eventsArray) {
[eventsArray enumerateObjectsUsingBlock:^(EKEvent *ekEvent, NSUInteger idx, 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);
[self updateAppointmentForEvent:ekEvent];
*stop = YES;
}
}];
}];
}];}
Instead of fetching all events, can you not update only the events that are onscreen/active.
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;
}
}];
}];
}