I am sure this question is duplicated somewhere, but I can't find a solution. I am making an app in which one feature allows the user to select the days and times they will receive a local notification.
They can select any time of the day they like, and can toggle the different days of the week (mon, tues, weds etc). The notifications will be sent weekly. I therefore limit the user to creating just 3 notifications - if all 7 days are selected I will set the repeatInterval to daily (one notification). If 6 days are selected for each 3 notifications then I will need an individual notification for each day (totalling 3x6=18 notifications). In all likelihood, only 1 notification will be used so this is fine.
I know how to set an notification for a certain time in the future, but how do I set a notification for say 6pm on a Monday?
Below is my code which I have been using for testing. It sets an alert for 4 seconds in the future (I was calling it from applicationDidEnterBackground).
NSDateComponents *changeComponent = [[NSDateComponents alloc] init];
changeComponent.second = 4;
NSCalendar *theCalendar = [NSCalendar currentCalendar];
NSDate *itemDate = [theCalendar dateByAddingComponents:changeComponent toDate:[NSDate date] options:0];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = itemDate;
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.repeatInterval = NSWeekdayCalendarUnit;
Exactly the same way you are doing right now, but create a date differently:
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setDay:1];
[comps setMonth:1];
[comps setYear:2013];
[comps setHour:10];
[comps setMinute:10];
[comps setSecond:10];
localNotif.fireDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
I have also searched about it. Below code work good for me. Pass the week day value 1 to 7 Sunday to Saturday and notification body with action which you want to fire and specify your date then notification will come on that specific day.Hope this help you.
-(void) weekEndNotificationOnWeekday: (int)weekday :(UILocalNotification *)notification : (NSDate*) alramDate
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *componentsForFireDate = [calendar components:(NSYearCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit| NSSecondCalendarUnit | NSWeekdayCalendarUnit) fromDate: alramDate];
[componentsForFireDate setWeekday: weekday] ; //for fixing Sunday
// [componentsForFireDate setHour: 20] ; //for fixing 8PM hour
// [componentsForFireDate setMinute:0] ;
// [componentsForFireDate setSecond:0] ;
notification.repeatInterval = NSWeekCalendarUnit;
notification.fireDate=[calendar dateFromComponents:componentsForFireDate];
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
I know how to set an notification for a certain time in the future,
but how do I set a notification for say 6pm on a Monday?
You can create an NSDate object representing 6pm on the next Monday with the approach showed in How to Get an NSDate for a Specific Day of Week and Time from an Existing NSDate. Then, if you want it to repeat on every Monday you can use localNotification.repeatInterval = NSWeekCalendarUnit. However I'm not sure it's going to work as expected with Daylight saving time.
I don't think you can schedule notifications with that much flexibility. Just schedule the next one anytime they change it, and when it fires schedule the next one coming up. Only one to worry about canceling and easy to setup.
Related
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.
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).
I have a notification system in my app that allows users to set multiple notifications and they can be:
Daily
Weekly
Monthly
The first 2 are ok, but on the third one I have a problem.
Lets say that we are on March and the user sets the notification to trigger on the 31st of the month. The notification is scheduled correctly, but if we where on April (30 days) for example the notification is scheduled on the 1st of May.
I have 2 questions:
How can I schedule notification on the last day of the month? Or set them up to handle this case gracefully in the following months.
If the notification on the 31st of March is scheduled correctly, will the next one be scheduled on the 30th of April and then on the 31st of May? My guess is no, will be 31 of march, 1st April and 31st of April.
Scheduling multiple notification is not an option for me because apple has a limit and that limit can be reached easily if the user has 6 monthly notifications (6x12) and they could have more than that.
Thanks,
Sergio
EDIT
Sorry I didn't explained myself properly.
I don't have the problem setting the notification on the correct day. But I have a problem with how the repetitions will work. If I set a notification to trigger on the 31st every month starting on the 31st of March (lets assume we are in March) the first one will come up on the right day, but what would happen on April?
Thanks again.
You can get the last day of the month of a specific date using the below methods
-(NSDate*)lastDayOfMonthOfDate:(NSDate *)date
{
NSInteger dayCount = [self numberOfDaysInMonthCountForDate:date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
NSDateComponents *comp = [calendar components:
NSYearCalendarUnit |
NSMonthCalendarUnit |
NSDayCalendarUnit fromDate:date];
[comp setDay:dayCount];
return [calendar dateFromComponents:comp];
}
-(NSInteger)numberOfDaysInMonthCountForDate:(NSDate *)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:TIMEZONE]];
NSRange dayRange = [calendar rangeOfUnit:NSDayCalendarUnit
inUnit:NSMonthCalendarUnit
forDate:date];
return dayRange.length;
}
Find the last day from these method and schedule notification for that day
Source : Getting the last day of a month
-[NSCalendar rangeOfUnit:inUnit:forDate:] will tell you the minimum and maximum values that a particular date component can take in a larger component, in the context of a particular date.
If you use NSDayCalendarUnit and NSMonthCalendarUnit, the length of the result will be the last day of the month containing the passed date.
For example:
NSCalendar * c = [NSCalendar currentCalendar];
NSRange aprilRanger = [c rangeOfUnit:NSDayCalendarUnit
inUnit:NSMonthCalendarUnit
forDate:[NSDate date]];
NSLog(#"%lu", aprilRanger.length);
NSDateComponents * minusTwoMonths = [NSDateComponents new];
[minusTwoMonths setMonth:-2];
NSDate * febDay = [c dateByAddingComponents:minusTwoMonths
toDate:[NSDate date]
options:0];
NSRange febRange = [c rangeOfUnit:NSDayCalendarUnit
inUnit:NSMonthCalendarUnit
forDate:febDay];
NSLog(#"%lu", febRange.length);
Produces:
30
28
And this will give you the correct answer when weird things like leap days happen, too.
What I had to do was:
Check if the current month has the day that the user requested (max 31).
If it doesnt then schedule a normal notification for the current month on the last day and then schedule the monthly one on the following month, the OS will handle the rest.
Note: If the current month doesn't have the requested day (31 of feb), the next one will definitely have it (31 of mar).
If it does then schedule the monthly on the current month.
Hey It's not a big deal.
In your notification object set repeatInterval to NSMonthCalendarUnit
UILocalNotification *notify = [ [UILocalNotification alloc] init ];
notify.fireDate = notificationDate;
notify.repeatInterval = NSMonthCalendarUnit;
// use NSDayCalendarUnit, NSWeekCalendarUnit for daily or weekly respectively.
Thanks
damithH
In iOS, I wanted to schedule a notification to inform the user after 48 hours since he closes the app. In addition to the 48 hours, it has to be shown at 7p.m. of the day.
For example:
Current time is 5 p.m, the user closes the app. Then the notification should popup after 2 days at 5 p.m as well, but I want it to delay until 7 p.m. How should I write my codes in order to achieve this with the mentioned criteria?
In - (void)applicationWillResignActive:(UIApplication *)application method,
calculate the actual time to be shown, i.e. 48 hours + hours to 7pm of that day.
cancel any current scheduled notification
create a UILocalNotification and set the parameters needed (time to fire and alert text).
Edit based on comment:
If I understand you this should be what you want. It calculates a time that is 48 hours later, and manually set the fire time to 7pm. If the calculated time is greater than 7pm, it will be fired on the next day.
NSDate *currentDate = [NSDate date];
NSDate *futureTime = [currentDate dateByAddingTimeInterval:60*60*48];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
[calendar setTimeZone:timeZone];
NSDateComponents *components = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit fromDate:futureTime];
if ([components hour] >= 19) { // make it the next day
[components setDay:[components day] + 1 ];
}
[components setHour:19];
[components setMinute:00];
NSDate *alertTime = [calendar dateFromComponents:components];
NSDateFormatter *DateFormatter=[[NSDateFormatter alloc] init];
[DateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *date =[NSDate dateWithTimeInterval:60*60*48 sinceDate:[NSDate date]];
NSString *dateString=[DateFormatter stringFromDate:date];
NSInteger timeLater=[dateString substringWithRange:NSMakeRange(11, 2)].integerValue;
if(timeLater<19)
date =[date dateByAddingTimeInterval:(19-timeLater)*60*60];
dateString=[DateFormatter stringFromDate:date];
NSLog(#"%#",dateString);
notification.fireDate =date.
I am implementing UILocalNotification to remind the first-time user at 8:00 PM to use the app again. The user can switch it completely off or setup a different time.
I picked 8:00 PM as time after work, so that it is less interrupting or intrusive.
I am not sure how to handle timezones.
If the user is in New York. [NSDate date] only returns a GMT datetime (a moment in time since the reference date). So how do I determine his 8:00 pm?
Could I just do this?
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *dateComps = [calendar components:(NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitTimeZone) fromDate:[NSDate date]];
[dateComps setHour:20];
[dateComps setTimeZone:[NSTimeZone defaultTimeZone]];
NSDate *result = [calendar dateFromComponents:dateComps];
From the UILocalNotification docs:
The date specified in fireDate is interpreted according to the value of this property. If you specify nil (the default), the fire date is interpreted as an absolute GMT time, which is suitable for cases such as countdown timers. If you assign a valid NSTimeZone object to this property, the fire date is interpreted as a wall-clock time that is automatically adjusted when there are changes in time zones; an example suitable for this case is an an alarm clock.
So to ensure your notification is set at the same 'wall-clock' time (eg 8pm) regardless of the time zone, just do
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = theDate;
localNotification.timeZone = [NSTimeZone defaultTimeZone];