Get time only of next iOS local notification - ios

What is the best way to get the time of the next local notification set for my app?
I know the following loop can be used to get notifications, but does this always sort in time order so I could just get time of item [0], or is it in order of when they were added? And how can just time be pulled from this? Will I need to get whole date and format the time out, or is there a better way?
UIApplication *app = [UIApplication sharedApplication];
NSArray *eventArray = [app scheduledLocalNotifications];
for (int i=0; i<[eventArray count]; i++)
{
UILocalNotification* oneEvent = [eventArray objectAtIndex:i];
//oneEvent is a local notification
//get time of first one
}
Many thanks!
Sam

This is really two problems. First, how to get the next notification. Second, how to get just the time components of that notification's date.
Number one, sorting an array by a date property of the contained objects
NSSortDescriptor * fireDateDesc = [NSSortDescriptor sortDescriptorWithKey:#"fireDate" ascending:YES];
NSArray * notifications = [[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingDescriptors:#[fireDateDesc]]
UILocalNotification * nextNote = [notifications objectAtIndex:0];
Two, get just the hours and minutes from the date
NSDateComponents * comps = [[NSCalendar currentCalendar] components:(NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit)
fromDate:[notification fireDate]];
// Now you have [comps hour]; [comps minute]; [comps second];
// Or if you just need a string, use NSDateFormatter:
NSDateFormatter * formatter = [NSDateFormatter new];
[formatter setDateFormat:#"HH:mm:ss"];
NSString * timeForDisplay = [formatter stringFromDate:[notification fireDate]];

You can't guarantee the order of the scheduledLocalNotifications array. If you need to grab the latest notification more than once in your app, I recommend creating a utility category on UIApplication with a method that contains the for loop. That way you can just call:
notif = [[UIApplication sharedApplication] nextLocalNotification];
Don't repeat yourself.

Related

IOS/Objective-C: Filter array of events with NSPredicate to find Dates close to now

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];

How to reference only the first object in indexPathsForVisibleRows

What I'm trying to do is detect which sections of a UITableView are visible, and then change the date of a calendar based on which are visible.
The issue is that there are typically multiple sections viewable at the same time, and I only want to change the date based on the first section index that appears in visibleRowIndexes, not all of them.
Here's my current implementation (Note: I run this function in cellForRowAtIndexPath):
-(BOOL)whatSectionsAreVisible {
NSArray *visibleRowIndexes = [self.agendaTable indexPathsForVisibleRows];
for (NSIndexPath *index in visibleRowIndexes) {
NSNumber *daySection = #(index.section);
static NSDateFormatter *dateFormatter = nil;
if(!dateFormatter){
dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = #"yyyy-MM-dd"; // Read the documentation for dateFormat
}
// Here is where I will map every index.section to an NSDate
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:daySection.intValue]; // <<== Extract int from daySection
[comps setMonth:6];
[comps setYear:2015];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDate *date = [gregorian dateFromComponents:comps];
// Update the day selected according to visible section
[[NSNotificationCenter defaultCenter] postNotificationName:#"kJTCalendarDaySelected" object:date];
// Store currentDateSelected
[self.calendar setCurrentDateSelected:date];
NSLog(#"The visible section has an index of: %ld", (long)index.section);
if (index.section == 0) {
NSLog(#"index.row is indeed 0");
return YES;
}
}
return NO;
}
I tried doing something like NSNumber *daySection = #(index[0].section); instead of NSNumber *daySection = #(index.section);, but that doesn't seem to work.
Don't use a loop if you just want the first visible row. Get the first object of visibleRowIndexes as an NSIndexPath. Then get the section from that.
The indexPathsForVisibleRows method returns the index paths in ascending order. So, get rid of the for loop and execute the code in it only for the first object:
NSArray *visibleRowIndexes = [self.agendaTable indexPathsForVisibleRows];
NSIndexPath *index = [visibleRowIndex objectAtIndex:0];

Detect next UILocalNotification that will fire

Is there a way to find the NSDate for the next local notification that will fire?
For example, I've set up three local notifications:
Notification #1: Set to fire yesterday at 3:00 PM with repeat interval of daily.
Notification #2: Set to fire today at 5:00 PM with repeat interval of daily.
Notification #3: Set to fire tomorrow at 6:00 PM with repeat interval of daily.
Given that is is currently 4:00 PM, the next local notification that will fire is notification #2.
How can I retrieve this local notification and get its date?
I know that I can retrieve these local notifications in an array, but how do I get the next one that will fire based on today's date?
The main goal for your task is to determine the "next fire date" after a given
date for each notification.
The NSLog() output of a UILocalNotification shows this next fire date,
but unfortunately it seems not to be available as a (public) property.
I have taken the code from https://stackoverflow.com/a/18730449/1187415 (with small
improvements) and rewritten that as a category method for UILocalNotification.
(This is not perfect. It does not cover the case that a time zone has been
assigned to the notification.)
#interface UILocalNotification (MyNextFireDate)
- (NSDate *)myNextFireDateAfterDate:(NSDate *)afterDate;
#end
#implementation UILocalNotification (MyNextFireDate)
- (NSDate *)myNextFireDateAfterDate:(NSDate *)afterDate
{
// Check if fire date is in the future:
if ([self.fireDate compare:afterDate] == NSOrderedDescending)
return self.fireDate;
// The notification can have its own calendar, but the default is the current calendar:
NSCalendar *cal = self.repeatCalendar;
if (cal == nil)
cal = [NSCalendar currentCalendar];
// Number of repeat intervals between fire date and the reference date:
NSDateComponents *difference = [cal components:self.repeatInterval
fromDate:self.fireDate
toDate:afterDate
options:0];
// Add this number of repeat intervals to the initial fire date:
NSDate *nextFireDate = [cal dateByAddingComponents:difference
toDate:self.fireDate
options:0];
// If necessary, add one more:
if ([nextFireDate compare:afterDate] == NSOrderedAscending) {
switch (self.repeatInterval) {
case NSDayCalendarUnit:
difference.day++;
break;
case NSHourCalendarUnit:
difference.hour++;
break;
// ... add cases for other repeat intervals ...
default:
break;
}
nextFireDate = [cal dateByAddingComponents:difference
toDate:self.fireDate
options:0];
}
return nextFireDate;
}
#end
Using that, you can sort an array of local notifications according to the
next fire date:
NSArray *notifications = #[notif1, notif2, notif3];
NSDate *now = [NSDate date];
NSArray *sorted = [notifications sortedArrayUsingComparator:^NSComparisonResult(UILocalNotification *obj1, UILocalNotification *obj2) {
NSDate *next1 = [obj1 myNextFireDateAfterDate:now];
NSDate *next2 = [obj2 myNextFireDateAfterDate:now];
return [next1 compare:next2];
}];
Now sorted[0] will be the next notification that fires.

Handling EKEventStoreChangedNotification notification

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.

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