I want to develop an alarm application in iOS. So far, I have created basic UI where the user can select the time when the alarm should trigger. The code schedules one local notification for this time using following code:
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title = #"Helloooooo...";
content.body = #"Time to wake up!";
content.sound = [UNNotificationSound defaultSound];
//create trigger
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:triggerDate repeats:NO];
NSString *identifier = #"test";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content trigger:trigger];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Something went wrong: %#",error);
}
}];
Now, when this notification is triggered, I want to play music (like an alarm tone). After lot of research I understood that there are no callbacks for the notification triggered event when the application is in background.
Another approach I tried in which the code tries to call the playAlarmTone method after certain timer:
UIApplication *app = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:lroundf(secondsBetween)
target:self
selector:#selector(playAlarmTone)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
But with this approach, the music is not playing if the alarm clock is set to more than 15 minutes from the current time.
I have following questions:
What are the methods/hacks to run specific task after time interval of "x" minutes?
What is the best approach to implement the alarm clock in iOS?
I would appreciate any suggestions and thoughts on this topic.
EDIT:
I found an alarm application on the App Store which can play alarm music for infinite time when alarm is triggered. How this app can determine when to start the alarm music?
You should specify the music name by setting the sound property to your content.
So instead of:
content.sound = [UNNotificationSound defaultSound];
You should set:
content.sound = [UNNotificationSound soundNamed:#"yourSoundName.mp3"];
Also, make sure that your music length is not longer than 30 seconds.
Did you already consider using NSTimer scheduledtimerwithtimeinterval? This could be used to perform an action after a time interval as per your need. Also, check this article http://andrewmarinov.com/building-an-alarm-app-on-ios/ from Andrew.
Related
I scheduled multiple Local Notification for the user. All of them are also delivered on their specified time. However, when I try to open any one of them from Notification Center, all of them are getting cleared.
In a rightful scenario, I don't want all them to be cleared from notification centre, only those which are tapped to be opened.
Also, i tried commenting below code from AppDelegate.m, but the issue still persists. [[UIApplicationsharedApplication]setApplicationIconBadgeNumber:0];
Can anyone tell me what could be the issue here due to which my scheduled notifications are cleared from Notification Center even when I'm tapping to open only one of them?
Below is the code I'm using to schedule Local Notifications -
NSDateComponents *components = [SSUtility hoursMinuteAndSectionsForDate:date];
NSInteger hour = [components hour];
NSInteger minute = [components minute];
NSLog(#"Hour %ld Min %ld ", hour,minute);
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];
/* Set notification */
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.body = body;
// content.categoryIdentifier=NSNotificationC;
content.sound = [UNNotificationSound defaultSound];
content.userInfo = userInfo;
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content
trigger:trigger];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
SSLOG(#"Something went wrong: %#",error);
}
}];
So i did't believe that this is a default behaviour so what i found:
if you use UNCalendarNotificationTrigger then all delivered reminders are deleted on tap
if you use UNTimeIntervalNotificationTrigger then delivered reminders remain on the notification center
so try to use the UNTimeIntervalNotificationTrigger instead of UNCalendarNotificationTrigger
NSTimeInterval timeInterval = [fireDate timeIntervalSinceDate:[NSDate date]];
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timeInterval repeats:NO];
In my case, Add some text to content.body solve the problem:
content.body = 'Any text'
I am using local notifications in my app to alert urgent messages to the user. What happens is the user receives a push notification, then a local notification is created and fired 60 seconds later with a time interval of 60 seconds. This works great and the urgent notification fires every 60 seconds as expected.
The problem is after about 20 minutes the local notification stops firing. This does not occur all the time and sometimes the notification can fire for hours with no problem. However, this problem seems to occur more often than not.
On iOS 9 we did not experience this problem at all and the notification would fire repeatedly overnight even, so I am thinking this might be something related to iOS 10?
I've also got a hunch that perhaps this has to do with memory pressure from the OS? If I attach to xCode and debug then the problem does not occur at all and will fire into infinity.
The code I use to create the notification is as follows:
[[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> * _Nonnull requests) {
dispatch_async(dispatch_get_main_queue(), ^{
//If notification already exists then kill it with fire
[self removeAllLocalNotifications];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = #"Urgent";
content.sound = [UNNotificationSound soundNamed:#"dingdingding.wav"];
content.categoryIdentifier = #"urgentCategoryId";
content.body = #"You have an urgent message.";
// Deliver the notification in scheduled amount of seconds.
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:60 repeats:YES];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:#"urgentMessageId" content:content trigger:trigger];
// Schedule the notification.
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Notification creation Error");
}
if (completionHandler) {
completionHandler(UIBackgroundFetchResultNewData);
}
});
}];
});
}];
Any help would be much appreciated as this is a very frustrating issue.
I'm firing a local notification. Since UILocalNotification class is deprecated in iOS 10, I have used UserNotifications.framework.
When I try to set the custom sound for the notification, the default sound is playing all time.
Here is my code:
- (IBAction)fireLocalNotification:(id)sender {
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:#"Hello!" arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:#"Hello_message_body"
arguments:nil];
content.sound = [UNNotificationSound soundNamed:#"sound.mp3"];
// Deliver the notification in five seconds.
UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
triggerWithTimeInterval:5 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:#"FiveSecond"
content:content trigger:trigger];
// Schedule the notification.
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError *error){
if(!error){
NSLog(#"Completion handler for notification");
}
}];
}
My sound is fine; sound.mp3 is present in project bundle itself.
More info:
https://developer.apple.com/reference/usernotifications/unusernotificationcenter?language=objc
Try deleting the app from the device, clean and run the app again on device.
Sometimes, resources are not properly updated; I think that is the problem in your case.
Long audio-files are not supported.
Is your file longer then 30 seconds? If yes, it will not work. Only files shorter then 30 seconds are available. If the file is longer then 30 seconds then it will be replaced with default sound.
UNNotificationSound documentation
In didFinishLaunchingWithOptions a timer loop calling a function httpRequest every 1 minute interval.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//rest of code
NSTimer *notifyTimer = [NSTimer timerWithTimeInterval:60 target:self selector:#selector(httpRequest) userInfo:nil repeats:YES];//7200.0
[[NSRunLoop mainRunLoop] addTimer:notifyTimer forMode:NSDefaultRunLoopMode];
return YES;
}
After pressing home button application is going to background and calling function applicationDidEnterBackground so a background task is starting.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
__block UIBackgroundTaskIdentifier bgTask;
UIApplication *app = [UIApplication sharedApplication];
expirationHandler = ^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
};
bgTask = UIBackgroundTaskInvalid;
bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
}
By httpRequest function I am geting Y from web server after every 1 minute interval so a UILocalNotification fires after every seconds.
-(NSString *)httpRequest {
NSURL *url = [NSURL URLWithString:#"http://192.168.10.67/t.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *userAgent = [NSString stringWithFormat:#"bgTaskTest-IOS"];
[request setValue:userAgent forHTTPHeaderField:#"User-Agent"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"content-type"];
[request setHTTPMethod:#"GET"];
[request setTimeoutInterval:25];
NSURLResponse *response;
NSData *dataReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSString *stringReply = [[NSString alloc] initWithData:dataReply encoding:NSASCIIStringEncoding];
if ([stringReply isEqualToString:#"Y"]) {
[self showLocalNotification:nil]; //calling UILocalNotification
} else {
NSLog(#"%#",stringReply);
}
return stringReply;
}
Function showLocalNotification is calling after every 1 minute based on response of httpRequest function.
-(void)showLocalNotification {
NSString *msg = #"test message";
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *_localNotification = [[UILocalNotification alloc]init];
_localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
_localNotification.timeZone = [NSTimeZone defaultTimeZone];
_localNotification.alertBody = msg;
_localNotification.soundName = UILocalNotificationDefaultSoundName;
_localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber]+1;
[[UIApplication sharedApplication] scheduleLocalNotification:_localNotification];
//[[UIApplication sharedApplication] presentLocalNotificationNow:_localNotification];
}
Everything is right, notification prompts after every 1 munite when application is in background.
But my problem is Background Task's life time is 10 mins, so after 10 mins no notification prompts. For this reason I am starting Background task again in beginBackgroundTaskWithExpirationHandler but my application kill at this time of restarting background task.
I couldn't able to use notification more than 10 mins when application is in background.
Please anybody help me.
There is no way (within the app store guidelines) to run arbitrary code in the background for longer than ten minutes (as you have noticed).
After 10 minutes your app will be suspended. There is a couple of ways around this, registering for other background modes (such as background audio, playing a silent sound file continuously) or background voip or background location services.
These hacky work around will keep your application unsuspended however your application will not get approved for the store.
in iOS7 there are advances to running code in the background, however nothing that will do what you want.
So if this is an app for your own use, use private API's or the method I suggested above, however if you want to get this app on the store, I'm afraid your out of luck.
I'm working on a VoIP-based IOS App.
There are two ways to play some sound to notify the user when a call come in:
Send UILocalNotification with sound. The sound will last for at most 30 seconds;
Play a local Music Asset in setKeepAliveTimeout:handler: function. But System just gives me 10 seconds to do the operation.
Is there any way to play the sound forever like the native Phone app?
I'm afraid #Dan2552 is correct.
Here's what Apple states:
Sounds that last longer than 30 seconds are not supported. If you
specify a file with a sound that plays over 30 seconds, the default
sound is played instead.
EDIT:
Playing an audio file for more than 30 seconds (or forever, for that sake) can be achieved by using AVAudioPlayer from AVFoundation
#import AVFoundation; // module -> no need to link the framework
// #import <AVFoundation/AVFoundation.h> // old style
- (void)playAudio
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"mp3"];
NSError *error = nil;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (!error) {
player.numberOfLoops = -1; // infinite loop
[player play];
} else {
NSLog(#"Audio player init error: %#", error.localizedDescription);
}
}
Then instead of setting the soundName property of the local notification you have to call this method on the main thread:
[self performSelectorOnMainThread:#selector(playAudio) withObject:nil waitUntilDone:NO];
The problem with Islam Q.'s answer is that AVAudioPlayer won't work for a notification since the app will be muted when you're not using the app. Only a UILocalNotification can let your app produce sound when it's not active. The 30 second limit is real. It should be done by repeating the notification with another 30 seconds notification. I don't know how long other VOIP applications will keep ringing but even with a traditional phone there is limit usually, something like a minute.
My strategy would be:
Send initial notification with 29 seconds alarm that somebody tries
to contact
Send second notification with a different 29 seconds alarm after 40 seconds that you are about to miss a call
Send a third notification that you missed a call after 100 seconds
This would allow the user 1:40 of time to respond to a call.
Your code would look something like this:
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"FIRST_CALL", #"%# is calling you!");
notification.soundName = #"normal_ring.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(40 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (!phone.isRinging)
{
// Somebody picked it up already in the mean time
return;
}
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"HURRY_CALL", #"Hurry, you are about to miss a call from %#!");
notification.soundName = #"hurry_ring.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (!phone.isRinging)
{
// Somebody picked it up already in the mean time
return;
}
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"MISSED_CALL", #"You've just missed a call from %#");
notification.soundName = #"missed_call.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
});
If you use the code, keep in mind you first need to make sure the app is in the background so the UILocalNotification would be the only option you have. Use a normal AVAudioPlayer when it's in the foreground, much more flexible. Second thing is that the app becomes foreground when the user responds to the call so it should mute the alert and let the AVAudioPlayer take over.
You want more or less a smooth transition between the alert and the in app sound to have a fully polished experience for your users so the best thing to do would be to measure the time between rings and let AVAudioPlayer start in exact the right time at the start of a new loop.
Your second problem is keeping the app alive in the background. This simple code will simply keep on polling the app every now and then to keep it alive. It comes straight from a working application that does just this, this code polls if a timer is still running and sleeps for five seconds when it is:
UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[timerExecuted timerUpdate];
while (!timerExecuted.isStopped)
{
[NSThread sleepForTimeInterval:5];
}
[application endBackgroundTask:backgroundTask];
});