Scheduling Local Notifications - ios

I'm working on a feature that will allow users to schedule days and a time for receiving a notification.
So far, the time and message feature is working great. Where I am stuck at is the repeat interval.
Here's what I have tried:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSString *formatedDate = [dateFormatter stringFromDate:self.datePicker.date];
self.currentTimeLabel.text = formatedDate;
NSDate *date = [dateFormatter dateFromString:self.currentTimeLabel.text];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:date];
NSInteger hour = [components hour];
NSInteger minute = [components minute];
[components setHour:hour];
[components setMinute:minute];
if ([self.currentRepeatLabel.text containsString:#"Sun"]) {
[components setWeekday:0];
self.notification = [[UILocalNotification alloc] init];
self.notification.fireDate = [calendar dateFromComponents:components];
self.notification.repeatInterval = NSCalendarUnitDay;
[self.notification setAlertBody:[NSString stringWithFormat:#"A friendly reminder: %#", self.titleStringToDisplay]];
if (self.soundSwitch.isOn == YES) {
self.notification.soundName = #"soundeffect.wav";
}
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:self.titleStringToDisplay forKey:#"title"];
self.notification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:self.notification];
}
if ([self.currentRepeatLabel.text containsString:#"Mon"]) {
[components setWeekday:1];
self.notification = [[UILocalNotification alloc] init];
self.notification.fireDate = [calendar dateFromComponents:components];
self.notification.repeatInterval = NSCalendarUnitDay;
[self.notification setAlertBody:[NSString stringWithFormat:#"A friendly reminder: %#", self.titleStringToDisplay]];
if (self.soundSwitch.isOn == YES) {
self.notification.soundName = #"soundeffect.wav";
}
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:self.titleStringToDisplay forKey:#"title"];
self.notification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:self.notification];
}
// I'm doing this for each day.
NSCalendarUnitDay is repeating my notification everyday, not matter what days I have selected. I have tried NSCalendarUnitWeekOfYear but my notifications never fire when using that.
The goal is for the user to set their title, time, and repeat days (much like the native alarm app).
How do I set the repeat interval for this?
Update and new issue:
I am using NSCalendarUnitWeekOfYear now.
Here's my issue ... the notification is no longer firing now and the repeatInterval is always set to Monday instead of the day of the week that it steps through in code.

There are several ways to do that . You can use NScalender to find the weekday and than calculate that date on which you want to be notifications fire.
-(void) calculateweeknumber
{
NSCalendar *numberCalendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [numberCalendar components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
[comps setSecond:0.0];
weekday = [comps weekday];
NSLog(#"number is %d",weekday);
}

Your code has many problems.
Your date comes from a UIDatePicker obviously. You should use this date. Converting it back and forth is nonsense.
Also, get rid of all that duplicated code. It makes understanding (and fixing) the code more difficult. It hides the logic.
The real problem though is that you just set date components as if it were a date. It is not. You are starting with some date thrown into components, and then setting a weekday. This will not give another valid date - why should it? How could it? What should it adjust? The day? The month? The year? Into the future or into the past? The weekday seems to be simply ignored when converting to a date.
You need to calculate a proper date (this answer describes calculating the next monday).

Related

Scheduled local notifications are irregular

I'm having significant trouble with my iOS local notifications. I have an app, written in Objective-C, that allows the user to pick the time the notification fires, as well as the days of the week (it should fire every week on the specified days at the specified time). I have no trouble with the time of day the notification fires, that works correctly. However, when I specify the day(s) it should fire, odd things happen. At first, they fire once, but every day rather than only the specified days. Then, after the first week, they fire every day, but not only once. Instead, they fire as many times as days are specified (so if Sunday, Monday and Tuesday are chosen, each day, the user will receive 3 consecutive notifications at the specified time).
This is the code I'm using to set up the notifications.
When the "Save" button is tapped, the first thing that happens is a clearing of all notifications, to make way for the new ones.
//cancels all notifications upon save
[[UIApplication sharedApplication] cancelAllLocalNotifications];
Next, I use NSDate, NSCalendar and NSCalendarComponents to get the specifics for the current time, as well as the components from my UIPicker (which is used to select the time of day)
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitWeekday|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:now];//get the required calendar units
Then, I get the units for the time from the UIPicker, and the actual time, also from the picker.
NSDateComponents *pickedComponents = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:picker.date];
NSDate *minuteHour = [calendar dateFromComponents:pickedComponents];
[[NSUserDefaults standardUserDefaults] setObject:minuteHour forKey:#"FireTime"];
After that, I set the text I want to show up in the notification
NSString *reminder = #"Reminder text!";
Next is the actual setup of the notification. They're all the same (with the day of the week changed, of course), so I'll just show the one for Sunday.
//sunday
UILocalNotification *localNotificationSunday = [[UILocalNotification alloc] init];
if ([sundayTempStatus isEqual:#"1"])
{
//permanently save the status
[[NSUserDefaults standardUserDefaults] setObject:#"1" forKey:#"Sunday"];
//set up notifications
//if it is past sunday, push next week
if (components.weekday > 1)
{
components.day = components.day + 7; //if already passed sunday, make it next sunday
}
//components.day = 1;
components.hour = [pickedComponents hour];
components.minute = [pickedComponents minute];
NSDate *fireDate = [calendar dateFromComponents:components];
localNotificationSunday.fireDate = fireDate;
localNotificationSunday.alertBody = reminder;
localNotificationSunday.timeZone = [NSTimeZone systemTimeZone];
localNotificationSunday.repeatInterval = NSCalendarUnitWeekOfYear;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotificationSunday];
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:#"0" forKey:#"Sunday"];
}
Any help is greatly appreciated, and if any additional info or code is needed, I'll gladly provide it.
When code becomes repetitive, it often becomes more error-prone. I've written out a simple method that should take care of the scheduling of the reminders.
- (void)scheduleNotificationForDayOfWeek:(int)dayOfWeek withPickedComponents:(NSDateComponents *)pickedComponents andReminderString:(NSString *)reminderString {
NSCalendar *calendar = [NSCalendar currentCalendar];
UILocalNotification *notification = [[UILocalNotification alloc] init];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitWeekday|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:[NSDate date]];
components.hour = [pickedComponents hour];
components.minute = [pickedComponents minute];
NSDateComponents *additionalComponents = [[NSDateComponents alloc] init]; // to be added onto our date
if ([components weekday] < dayOfWeek) {
additionalComponents.day = (dayOfWeek - [components weekday]); // add the number of days until the next occurance of this weekday
} else if ([components weekday] > dayOfWeek) {
additionalComponents.day = (dayOfWeek - [components weekday] + 7); // add the number of days until the next occurance of this weekday
}
NSDate *fireDate = [calendar dateFromComponents:components];
fireDate = [calendar dateByAddingComponents:additionalComponents toDate:fireDate options:0]; // add on our days
notification.fireDate = fireDate;
notification.alertBody = reminderString;
notification.timeZone = [NSTimeZone systemTimeZone];
notification.repeatInterval = NSCalendarUnitWeekOfYear;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
What this does is actually pretty simple. Call it with a day of the week (e.g., 0 = Sunday, 1 = Monday), and it will schedule a repeating reminder for that day. I couldn't reproduce your issues in my tests with your Sunday code, so I figured that somewhere among repeating that code you must have made an error.
In this method, the fire date getting has been simplified. It uses NSDateComponents to easily get the next occurrence of that weekday. Call it like this: [self scheduleNotificationForDayOfWeek:0 withPickedComponents:pickedComponents andReminderString:#"Hello, world!"]; (which would show "Hello, world!" every Sunday at the specified components)
With this snippet, you should be able to get rid of most of the repeated statements in your code, and simplify how setting notifications is done. For me, this worked perfectly.

Scheduled local notification fires every day instead of once per week

I am building an iOS app that makes use of local notifications to remind the user to complete a task, but only on the certain days (chosen by the user). The time at which the notification should fire is set by the date picker. My code is based on some code from another SO post.
Say the user would like to be reminded to complete said task on Sundays. The first thing I do is find out what the current date is, and get the correct components from it, then get the correct components from the date picker for the hour and minute.
//set up calendar and correct components
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitWeekday|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:now]; //get the required calendar units
NSDateComponents *pickedComponents = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:picker.date];
NSDate *minuteHour = [calendar dateFromComponents:pickedComponents];
[[NSUserDefaults standardUserDefaults] setObject:minuteHour forKey:#"FireTime"];
NSString *reminder = #"Don't forget to check which customers are on vacation!";
Next, the data is saved, and the notification set up. This code is the same for every day of the week, with only the day name changed.
When the tempStatus is equal to "1", the user has chosen that day as a day to remind them.
//Save data
//sunday
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
if ([sundayTempStatus isEqual:#"1"])
{
//permanently save the status
[[NSUserDefaults standardUserDefaults] setObject:#"1" forKey:#"Sunday"];
//set up notifications
//if it is past sunday, push next week
if (components.weekday > 1)
{
components.weekOfYear++; //if already passed sunday, make it next sunday
}
components.hour = [pickedComponents hour];
components.minute = [pickedComponents minute];
NSDate *fireDate = [calendar dateFromComponents:components];
localNotification.fireDate = fireDate;
localNotification.alertBody = reminder;
localNotification.timeZone = [NSTimeZone systemTimeZone];
localNotification.repeatInterval = NSCalendarUnitWeekOfYear;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:#"0" forKey:#"Sunday"];
}
Thanks in advance, and I will gladly provide more information and code if necessary.

NSDateFormatter and NSDateComponents return different values for the week number

I'm making an app that creates a timetable. You can create a new one every week of the year. When the app has finished loading, it needs to load up the current week (For example: If it's the 1st of January, week 1 needs to be shown). I use NSDateFormatter to determine what the current week is.
NSDateFormatter
(I was testing this on the 9th of August 2016)
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"ww"];
int currentWeek = [[time stringFromDate:[NSDate date]] intValue];
I wanted to check if it was working so I used NSLog.
NSLog(#"%i", currentWeek);
It returned 32.
NSDateComponents
So NSDateFormatter thinks the current week is 32. So far, so good. The app needs to send a push notification to tell the user that a certain period is about to begin. So the app schedules a notification using NSDateComponents.
// Setting the notification's fire date.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setWeekday:3]; // Because it's on a Tuesday
[dateComps setWeekOfYear:currentWeek]; // 32
[dateComps setYear:2016];
[dateComps setHour:12];
[dateComps setMinute:20];
NSDate *theFireDate = [calendar dateFromComponents:dateComps];
// Creates the notification.
UILocalNotification *Alert = [[UILocalNotification alloc] init];
Alert.timeZone = [NSTimeZone defaultTimeZone];
Alert.alertBody = #"This is a message!";
Alert.fireDate = theFireDate;
[[UIApplication sharedApplication] scheduleLocalNotification:Alert];
And I also used NSLog.
NSLog(#"%#", theFireDate);
But it returned 2016-08-02 12:20:00 +0000 which is not the current date. It actually is the current date minus 7 days, or one week earlier. So does this mean that the current week is actually 33 instead of 32, which means NSDateFormatter is wrong? Or is it actually 32 which means NSDateComponents is wrong. And what causes the difference between those two?
Don't use NSDateFormatter, use NSCalendar which is much more accurate.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSInteger weekOfYear = [calendar component:NSCalendarUnitWeekOfYear fromDate:[NSDate date]];

How to schedule more local notifications in iOS when notification crosses limit of 64 [duplicate]

This question already has answers here:
Schedule number of Local Notifications
(6 answers)
Closed 7 years ago.
I am developing a calendar app which consists of multiple events(e.g 500 events) which consists of birthdays.I am downloading events form web service. I want to give user the alert on each birthdate at a specific time sat 10:00 AM on the birthday. I am using local notification for scheduling a alert but I am stuck as iOS allows only 64 notifications per app and I have multiple birthdays. I don't know how to schedule more notifications once the app crosses the limit. Please suggest how would I solve this problem. below is my code to schedule notifications
- (void)scheduleNotification:(NSMutableArray *)datesArray withMessage:(NSMutableArray *)messagesArray NotificationID:(NSString *)notificationID
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
NSDateFormatter *formatter1 = [[NSDateFormatter alloc]init];
[formatter1 setDateStyle:NSDateFormatterMediumStyle];
[formatter1 setDateFormat:#"dd/MM/yyyy"];
// NSDate *myTime = [formatter dateFromString:#"06:10 PM"];
NSDate *myTime = [formatter dateFromString:fireTime];
for (int i = 0; i < [datesArray count]; i++)
{
NSDate *myDate = [formatter1 dateFromString:[datesArray objectAtIndex:i]];
NSDateComponents *dateComponents = [calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:myDate];
NSDateComponents *timeComponents = [calendar components:NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:myTime];
NSDateComponents * newComponents = [[NSDateComponents alloc]init];
NSDateComponents *todayComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit fromDate:[NSDate date]];
int day = [dateComponents day];
int month = [dateComponents month];
[newCompoents setDay:[dateComponents day]];
[newCompoents setMonth:[dateComponents month]];
if (day >= [todayComponents day] && month >= [todayComponents month]) {
NSLog(#"day = %d, month = %d", day, month);
[newComponents setYear:[todayComponents year]];
} else {
NSLog(#"%d, %d", day, month);
[newComponents setYear:[todayComponents year]+1];
}
[newComponents setHour:[timeComponents hour]];
[newComponents setMinute:[timeComponents minute]];
NSDate *combDate = [calendar dateFromComponents: newComponents];
localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.fireDate = combDate;
localNotif.timeZone = [NSTimeZone defaultTimeZone];
// Notification details
NSString *message = [#"Wish" stringByAppendingFormat:#" %#%#", [messagesArray objectAtIndex:i],#" On His Birthday"];
localNotif.alertBody = message;
// Set the action button
localNotif.alertAction = #"View";
localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber = 1;
// Specify custom data for the notification
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:notificationID forKey:notificationID];
localNotif.userInfo = infoDict;
// Schedule the notification
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}
}
correct me if I am making any mistakes and suggest how to schedule more notifications once 64 limit is crossed.
I have a similar problem and a few additional options that may help you, though they all have flaws that means correct behavior is not guaranteed.
Keep in mind that overlapping birthdays could be useful here. If two people end up having a birthday on the same day, cancel and reschedule with both of their info associated. You could find other intervals where repetition occurs if you're OK w/ a generic message.
Obviously, encouring your user to press the notification to open the app would help (possible with your ad revenue too). You could try a nice message for your last one.
It's hacky, but you could use geofencing (but probably not remote notifications) as described here: Can push notifications be used to run code without notifying user? . If you add a feature that uses it, you might even be able to get it approved.
I've considered passing a custom calendar as well, but NSCalendar is toll free bridged to CFCalendarRef and the story seems to be that I'm not going to have luck trying to dig into that (and getting it approved at least). I would be happy to be told I'm wrong.

How to allocate custom time in UILocalnotification?

- (void)scheduleNotification :(int) rowNo
{
[[UIApplication sharedApplication] cancelAllLocalNotifications];
Class cls = NSClassFromString(#"UILocalNotification");
if (cls != nil) {
UILocalNotification *notif = [[cls alloc] init];
notif.timeZone = [NSTimeZone defaultTimeZone];
NSString *descriptionBody=[[remedyArray objectAtIndex:rowNo]objectForKey:#"RemedyTxtDic"];
NSLog(#"%#",descriptionBody);
notif.alertBody = [NSString stringWithString:descriptionBody];
notif.alertAction = #"Show me";
notif.soundName = UILocalNotificationDefaultSoundName;
notif.applicationIconBadgeNumber = 1;
NSDictionary *userDict = [NSDictionary dictionaryWithObject:notif.alertBody
forKey:#"kRemindMeNotificationDataKey"];
notif.userInfo = userDict;
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
}
I have a column name frequency which is fetched from Sqldb where it contains the number of times notification should appear for a particular cell.
if frequency = 3 ..the notification should fire say 8 AM , 2PM then 8PM
if frequency = 4 ..the notification should fire say 8 AM , 12PM , 4PM then 8PM.
Is there a way to do it? If anyone can help me that would be great
Unfortunately, You can specify value for repeatInterval only of type NSCalendarUnit (day, week, month). So, I think, You need to create several Notifications with different fireDate, and specify for them repeatInterval = NSDayCalendarUnit
For example,
NSDate *currentTime = [NSDate date];
notification1.fireDate = [NSDate dateWithTimeInterval:SOME_INTERVAL sinceDate:currentTime];
notification1.repeatInterval = NSDayCalendarUnit;
notification2.fireDate = [NSDate dateWithTimeInterval:SOME_INTERVAL * 2 sinceDate:currentTime];
notification2.repeatInterval = NSDayCalendarUnit;
After user viewed some of notifications - You can cancel them.
Update.
You can also create NSDate from different components like this:
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeekday:2]; // Monday
[components setWeekdayOrdinal:1]; // The first Monday in the month
[components setMonth:5]; // May
[components setYear:2013];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:components];
Also You can set hour, minutes, seconds, timezone and other parameters.

Resources