Phonegap Local Notifications in iOS

I was working on an app recently that required me to remind a user of something via a localNotification from phonegap. While there is a plugin that works great its documentation is lacking to say the least… Here is the proper way to schedule notifications and have them appear if the app is offline, and how to resume your app with a callback!

Since originally writing this I made some changes to the actual LocalNotification plugin that now allow for sound and proper callbacks.

So let’s get started!

While we’re waiting for my changes to be accepted to the phonegap plugins we will use my GIT repository for the new plugin -
https://github.com/DrewDahlman/Phonegap-LocalNotification

 

 

Setup

1. Create a new phonegap project in xCode.

2. Download the project from GIT

3. In your www folder create 3 folders.

  • js
  • images
  • css

4. inside of js create 2 folders

  • core
  • plugins

5. Copy the images, core, and plugins from the GIT project to your folders.

6. create a new file in js called init.js

Your file structure should like this now.

 

 

 

 

 

7. Now drag the LocalNotification.m and LocalNotification.h files and dag those into xcode into the plugins folder.

8. Go to your phonegap.plist and add a new plugin

  • value: LocalNotification
  • key: LocalNotification

Keeping up? Okay now let’s make our app!

HTML

<script charset="utf-8" type="text/javascript" src="phonegap-1.2.0.js"></script>
 
    <!-- CORE JS FILES --><script charset="utf-8" type="text/javascript" src="js/core/phonegap-1.2.0.js"></script><script charset="utf-8" type="text/javascript" src="js/core/jQuery.js"></script>
 
    <!-- PLUGINS --><script charset="utf-8" type="text/javascript" src="js/plugins/LocalNotification.js"></script>
 
    <!-- OUR INIT --><script charset="utf-8" type="text/javascript" src="js/init.js"></script>
 
    <!-- STYLE STUFF -->
localNotification + 60 seconds
localNotification
localNotification tomorrow
clear all

now let’s make it pretty with some CSS. Create a new file in your css folder called style.css
CSS

* {
	margin:0px;
}
body {
	font-family:Helvetica;
	-webkit-user-select: none;
	background-color:#fff;
}
#wrapper {
	width:100%;
	height:100%;
	padding-top:15px;
}
#header {
	height:150px;
	width:100%;
	background-image:url(../images/header.png);
	background-size:100%;
	margin-bottom:10px;
	box-shadow: 0px 0px 2px .6px #222222;
}
.btn {
	font-size:18px;
	text-align:center;
	padding-top:12px;
	padding-bottom:12px;
	width:95%;
	border-radius: 5px;
	background-color:#545454;
	margin:auto;
	color:#fff;
	font-weight:bold;
	box-shadow: 0px 0px 2px .6px #222222;
	margin-top:5px;
}

Before we get into the Javascript let’s talk about the localNotification plugin for phonegap. This plugin is great because it lets us schedule notifications, essentially creating an alarm that can go off when ever we want. The problem is that the plugin doesn’t really have too much documentation, though it is pretty straight forward it can be a bit tricky at first.

When you schedule a notification you need to send it a time formatted in military time like this

time = 13:30; // 1:30pm

BUT when you send this to the plugin you’ll notice in your console that it says -

localNotifications[91788:207] Notification Set: 2012-01-26 20:30:01 +0000 (ID: 123, Badge: 1)

The thing you need to remember is that the phone logs the time with no timezone so +0000 BUT the alarm will still go off based on your phones time. This is where it can become slightly confusing.

So now the guts of our app. We will start by creating our app object.

// APP
var app = {
	bodyLoad:function(){
		document.addEventListener("deviceready", app.deviceReady, false);
	},
	deviceReady:function(){
 
	},
	init:function(){
 
	},
	background:function(){
 
	},
	running:function(){
 
	}
};

Now that that’s in place we will add the heart of our notification system. I like to create one object that controls notifications.

// NOTIFICATION CENTER
var notification = {
	init:function(){
 
	},
 
	// This will fire after 60 seconds
	local_min:function(){
		var d = new Date();
		d = d.getTime() + 3*1000; //60 seconds from now
		d = new Date(d);
		plugins.localNotification.add({
			date: d,
			message: 'This just fired after a minute!',
			hasAction: true,
			badge: 1,
			id: '123',
			sound:'horn.caf',
			background:'app.background',
			foreground:'app.running'
		});
	},
 
	// This will fire based on the time provided.
	// Something to note is that the iPhone goes off of 24hr time
	// it defaults to no timezone adjustment so +0000 !IMPORTANT
	local_timed:function(hh,mm){
		// the example time we provide is 13:00
		// This means the alarm will go off at 1pm +0000
		// how does this translate to your time? While the phone schedules 1pm +0000
		// it will still go off at your desired time base on your phones time.
 
		console.log(hh+" "+mm);
		// Now lets make a new date
		var d = new Date();
			d = d.setSeconds(00);
			d = new Date(d);
			d = d.setMinutes(mm);
			d = new Date(d);
			d = d.setHours(hh);
			d = new Date(d);
		plugins.localNotification.add({
			date: d,
			message: 'This went off just as expected!',
			hasAction: true,
			badge: 1,
			id: '123',
                        foreground: 'app.foreground',
                        background: 'app.background'
		});
	},
	clear:function(){
		plugins.localNotification.cancelAll();
	},
	tomorrow:function(hh,mm,days){
		// Now lets make a new date
		var d = new Date();
			d = d.setSeconds(00);
			d = new Date(d);
			d = d.setMinutes(mm);
			d = new Date(d);
			d = d.setHours(hh);
			d = new Date(d);
 
			// add a day
			d = d.setDate(d.getDate()+days);
			d = new Date(d);
 
		plugins.localNotification.add({
			date: d,
			message: 'This went off just as expected!',
			hasAction: true,
			badge: 1,
			id: '123',
                        foreground: 'app.foreground',
                        background: 'app.background'
		});
	}
 
}

So after looking at that let’s talk about what we’re doing. We’ve got four functions:

  • local_minute
    • creates a notification 60 seconds in the future
  • local_timed
    • creates a notification based on a time you set
  • clear
    • clears all alarms
  • tomorrow
    • creates a notification that will go off at a specified time tomorrow

We are calling these functions from our HTML on each button there is an onClick function.

So if you run your app at this point you should be able to fire all of these notifications and see the results. But a catch for these is that if you’re out of the app and click the notification to open the app again it just resumes from where it left off, nothing happens. If you’re in the app nothing happens at all.

Using the new plugin we now have the ability to attach callbacks to our LocalNotification! take a look at what a full call to LocalNotification looks like -

var d = new Date();
	d = d.getTime() + 3*1000; //60 seconds from now
	d = new Date(d);
	plugins.localNotification.add({
		date: d,
		message: 'This just fired after a minute!',
		hasAction: true,
		badge: 1,
		id: '123',
		sound:'horn.caf', // define our sound
		background:'app.background', // our background callback
		foreground:'app.running' // our foreground callback
	});

These new options will allow you to really customize the experience for the user, we will need to make some minor changes however to our AppDelegate file in order to process the callbacks.

If you copy this code and past it at the end of your AppDelegate.m file in xcode classes folder, right before the @end

// ADD OUR NOTIFICATION CODE
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification 
    {
 
        UIApplicationState state = [application applicationState];
        if (state == UIApplicationStateActive) {
            // WAS RUNNING
            NSLog(@"I was currently active");
 
            NSString *notCB = [notification.userInfo objectForKey:@"foreground"];
            NSString *notID = [notification.userInfo objectForKey:@"notificationId"];
 
            NSString * jsCallBack = [NSString 
                                     stringWithFormat:@"%@(%@)", notCB,notID];  
 
 
            [self.viewController.webView  stringByEvaluatingJavaScriptFromString:jsCallBack];
 
            application.applicationIconBadgeNumber = 0;
        }
        else {
            // WAS IN BG
            NSLog(@"I was in the background");
 
            NSString *notCB = [notification.userInfo objectForKey:@"background"];
            NSString *notID = [notification.userInfo objectForKey:@"notificationId"];
 
            NSString * jsCallBack = [NSString 
                                     stringWithFormat:@"%@(%@)", notCB,notID]; 
            [self.viewController.webView stringByEvaluatingJavaScriptFromString:jsCallBack];         
 
            application.applicationIconBadgeNumber = 0;
        }                 
    }

What this code does is evaluate the notification and the state of the application. Was it running or was it in the background. This is great because Phonegap cannot do any background processing, so we need to piggy back on the native system to do that for us.

What this also does is make a call to a function of our choosing depending on the state the app was in. If we’ve defined a callback in our LocalNotification.add function then it that will be called when the app resumes from that notification.

This also resets the badge number to 0.

A problem that will sometimes happen is that your app will crash on resume from a notification, while this problem seems to be quite random it can be annoying if it does happen. Luckily a solution was found for this! Samuel Michelot on Stackoverflow shared his solution for the problem.

In your AppDelegate.m file we have to comment out a few lines in our “didFinishLaunchingWithOptions” method -

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //commented out because it makes the app crash at startup with local notification...
    /*NSArray *keyArray = [launchOptions allKeys];
    if ([launchOptions objectForKey:[keyArray objectAtIndex:0]]!=nil)
    {
        NSURL *url = [launchOptions objectForKey:[keyArray objectAtIndex:0]];
        self.invokeString = [url absoluteString];
        NSLog(@"Mosa_fr_en-busi launchOptions = %@",url);
    }*/
 
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

ADDING SOUND
The coolest piece about the new localNotification plugin is that you can attach a sound to play when the notification fires, vs using the default sound. To do this we will need to do a couple things first.

1. All sounds must be less than 30 seconds long.
2. We will need to convert the sound to a .caf file.

To convert the sound if you are on a mac just use iTunes to first convert it to an .aif, then in finder change the file extension to a .caf and you’re all set.

3. Once you’ve converted drag the file into your “resources” folder in xcode.

Now save your files, compile and run! You’re all set!

DOWNLOAD PROJECT

This entry was posted in phonegap and tagged , , . Bookmark the permalink.

49 Responses to Phonegap Local Notifications in iOS

  1. Joe Lones says:

    Great post!

    I was wondering if you’re solution can be tweaked such that it could enable a php server to send a push notification to a jqm app? Would such a solution be difficult to implement? How much of your solution can be reused?

    Thank you

  2. Chris Brody says:

    Excellent writeup. In the stack overflow question about the iOS LocalNotification app crashes in didFinishLaunchingWithOptions, there were a couple excellent answers to handle the conditions on launchOptions properly instead of just commenting out: http://stackoverflow.com/a/8649605 and http://stackoverflow.com/a/9042812

  3. Riccardo says:

    Hello =)

    THIS IS A GOOD ARTICLE !!!

    Could I delete a single notification ? =)

    THANKS in advance!

    • Drew says:

      Thanks! Yes you totally can! If you use

      plugins.localNotification.cancel('123');

      Just use the ID you gave the notification

  4. Riccardo says:

    Wow, fast reply =)

    Also an other thing.. can I show in a page the saved notifications?

    =)

    sorry for bothering you =)

    • Drew says:

      hmm what you could do is create a localStorage for each notification, and display those…

      You could change the callback function to also include the ID to remove it from the localstorage. so if your callback is app.foo() just change it to app.foo(’123′) and then when the callback happens it removes that from LS…

  5. Riccardo says:

    Yeah =) I think I’ll use JSTORAGE to do this =)

    Anyway, THANKS ! =)

  6. Claes Gustavsson says:

    Hi Drew
    And thanks a lot for a great plugin!
    I have a couple of questions about it!
    1. How do I remove the bagde ones the notification has been showed and I have opened the app?
    It is still visible on the icon after I have visited the app. Do I have to have phonegap-1.2.0.js?
    Im running phonegap-0.9.6.js
    2. You have 2 phonegap-1.2.0.js files in your code above, is the js/core/phonegap-1.2.0.js different?
    3. I would like to run a notification on a set date eg: on friday the 17 of feb at 15.00 and I would like to repeat it every friday thereafter, how do I set that up? With notification.tomorrow(02,17,1); in the onDeviceReady function – is this right and how do I set the time?
    4. I dont get an alert pop up as you show on your image at the top of this page, I get a notification at the top of my screen, is this the way it should work?
    5. Is it somehow possible to load a new message from my server into the “message: ‘This went off just as expected!’,” with jquery .load or something, that way you could run the notification every friday at 15.00 with a new message, it would be great.
    Or maybe load both the message and the notification.local_timed(09,30); from the server with .load or getscript maybe, so you could set a new date, a new message and a new time. So every time the app goes from resume you can get the next notification message, date and time?
    I got no idea if its possible, but it should….I think :-)

    Again, thanks a lot!

    • Claes Gustavsson says:

      I have now updated my code with your other updated post and changed everything except the init.js file and it works, with the callbacks and all, but now there is no badge at all on the app icon? What Im I missing?
      Thanks a lot!

      • Drew says:

        Hi Claes,
        The plugin is going to automatically remove the badge from the app. It’s set to do this once the app has been opened from the notification. You could remove the code in the AppDelegate.m file -

        application.applicationIconBadgeNumber = 0;

        That’s where you can adjust that.

        • Claes Gustavsson says:

          Thanks a lot Drew
          But what I mean is that the badge is never displayed?
          If the phone is active and there is a notification I guess that it will show a badge on the icon?

          I wonder if you please can answer my other questions aswell from the above question, no 2-5, specially question no 3.
          Thanks a lot for your help.

  7. Eric Holmes says:

    Greetings Drew!

    Fantastic article, it really helped the setup process. I am having issues setting up the callback functionality. I copied and pasted the code that you put in for the AppDelegate.m file, is there something I should also be including into the AppDelegate.h file?

    I am getting this error:

    Request for member “webView’ in something not a structure or union

    • Drew says:

      Hi Eric,
      Have you made sure that you’ve installed the plugin correctly? Also what version of phonegap are you running?

  8. Glory says:

    Thanks for great work! But it doesn’t work in my environment. ( Xcode 4.2 , phonegap 1.3.0 ). I’ve checked that the events calling localnotification are correctly working. What could I do wrong? Is there any special environment setup to use this plugin?

    • Glory says:

      Debuger output is as blow when I click ‘localnotification + 60 seconds’ button ( where I changed it into 10 seconds ).

      2012-02-22 20:26:20.277 noti[2987:207] Notification Set: 2012-02-22 11:26:30 +0000 (ID: 123, Badge: 1, sound: horn.caf,callback: app.background())
      2012-02-22 20:26:30.002 noti[2987:207] I was currently active
      2012-02-22 20:26:30.005 noti[2987:207] [INFO] I am currently running, what should I do?

      • Drew says:

        That looks correct…. In order to get the full notification close the app and allow the 10 seconds to go by and you should get an alert when you open it, it will fire the call back function…

  9. Stuart says:

    Great article, much appreciated.
    I can see the notifications in the Notification centre but I don’t get any other alerts at all. I don’t see the banner or the phone wake when asleep etc. Is this correct behavour or am I missing something.

  10. Stuart says:

    The timed ones seem to work fine.

  11. Arnau says:

    I have no notification working at all… I get the console log when I set a Notification, but I don’t get the “I was in the background but i’m back now!” nor the “I am currently running, what should I do?” console logs. I’m really new at iOS dev. I’m missing something? Maybe because I’m using the emulator?

    I’m using phonegap-1.4.1.js and jquery-1.7.1.js

    Thanks!

    • Drew says:

      A couple things.
      1. Have you tested by exiting the app and letting it go off while it’s in the background?

      2. Phonegap 1.4+ is different than any other releases of phonegap which has screwed things up in the plugin making world. I am in the middle of re-writing my plugins for 1.5. At the moment there are plugins for 1.3 and lower and 1.4 and higher.

      If 1 isn’t working then it’s either an issue with how the plugin was installed or the version of phonegap.

      • Arnau says:

        I don’t know exactly what I made wrong, but I’ll started from the scratch again and now the notifications work. What doesn’t work is in the snippet added in AppDelegate.m, it does not find the “self.webView”.

        On the other hand, the 60 sec fired not is not working because the time is actually in 3 seconds:

        d = d.getTime() + 3*1000; //60 seconds from now

        And should be a 60 instead of a 3 ;D

        Anyhow, thank you a lot for your job, it’s really really helpful.

        • Drew says:

          Yeah that sounds like a 1.4 issue. Like I said they changed things quite a bit and have depreciated a lot of things, and have changed phonegap to be used as a component. As I was re-writing plugins for 1.4 they released 1.5 which requires a different way as well. SO, my suggestion is to use 1.3 or go to 1.5 but be aware that plugin support is not all the way yet, as every dev has to re-write their plugins.

  12. James says:

    Hello. Thanks for the great tutorial. It’s perfect for today as this is a topic I’m needing to tackle with my PhoneGap application. I wonder though, I’m really wanting to add an image to my local notification (due to the app I’m developing) and I wonder if you knew at all how to approach it? And if it is possible at all?

    I found this StackOverflow topic on it: http://stackoverflow.com/questions/4941949/how-can-i-show-image-in-alert-body-of-local-notification and it seems to be possible… The main answer was “…we found that we cant customize the UILocalNotification, so i have handled this in applicationDidRecievelocal otification by showing custom UIAlertView.”

    Do you know (or anyone) know how I could approach this? It would be great to add an image to the alert box local notification. Thanks, hope someone could help me!

    • Drew says:

      That is an interesting question! With localNotifications you won’t be able to attach an image, what you could do is have an image called when the app is opened from the notification…

  13. Diego says:

    Hi, great plugin!

    Is this plugin working on cordova applications?

    • Drew says:

      Hi Diego,
      We made an update to it to allow for Cordova, but it has not been completely tested yet. I am planning on doing an update on all plugins soon but not totally sure when.

      I would recommend that you use an older version of phonegap if possible as the 1.5 release is rather buggy and has the least amount of support.

  14. Steeler E says:

    In order to make the badge work I had to change the following line in “LocalNotification.m” from:

    notif.applicationIconBadgeNumber = 0;

    To:

    notif.applicationIconBadgeNumber = badge;

    I am not sure if this helps anybody or not. Great tutorial!

  15. Herb Abrams says:

    Many thanks for your work on this.

    Should this be: @”foreground”]; in AppDelegate.m ?

    NSString *notCB = [notification.userInfo objectForKey:@"forground"];

    Also, can you point me to a resource that explains how I could specify a ‘sound only’ local notification – no banner, no alert box, no badge?

    Herb

    • Drew says:

      the forground is a misspelling, I will adjust it in the mast project on GIT but basically you will need to change the forground in the appDelegate as well as plugin js and in your call to the plugin.

      It is not possible to play a sound without a notification, the way this works is by scheduling it with the system, the user needs to have some idea of what app made the noise.

      cheers!
      Drew

  16. Sama says:

    Hello!
    Thanks for your work!
    is possible to test it on simulator??

    thanks!

  17. Sama says:

    Hi!

    I’m trying to test your phonegap project on a iPhone 3g with fmw 4.2.1.
    I have compiled the project with SDK 3.2 and iOS deployment target iOs 3.0 but didn’t work when i click “localNotification” or “localNotification +60 seconds” can you help me pls?? Thanks a lot!

  18. Adam says:

    I get a failed build on 1.5.0 – Property ‘web view not found on object of type AppDelegate *”

    • Drew says:

      The current plugin is made for Cordova 1.6+, you can use the other iPhone version which is made for 1.4<

      I highly recommend you use 1.6 as the phonegap team fixed quite a few major bugs!

      • Sama says:

        Thanks a lot, i have upgraded my cordova lib and now it work very well!
        If the notification.local_timed(10,16) is set, it will show every day at 10.16 or it will show only one once?
        It is possible to not show the red ball notification on the icon?

        Thanks for your help

        • Drew says:

          Awesome, so a couple things -
          In the example the function local_timed() means it will go off at 10:16 daily because the call is

          plugins.localNotification.add({
          			date: d,
          			repeat:'daily',
          			message: 'This went off just as expected!',
          			hasAction: true,
          			badge: 1,
          			id: '123',
          			sound:'horn.caf',
          			background:'app.background()',
          			foreground:'app.running()'
          		});
          

          The badge should show up as long as the app hasn’t been opened, when the app is opened back up the badge is removed.

      • Adam says:

        To be honest . The last stable version was phone gap 1.3 ..
        Had nothing but errors and issues 1.6-17…

        I might revert back .

  19. Mark says:

    To anyone having difficulties with Property ‘web view not found on object of type AppDelegate *”

    Try adding [self.viewController.webView stringByEvaluatingJavaScriptFromString:jsCallBack];

  20. Dave says:

    Outstanding tutorial and project. Thanks for the help!

  21. Jack Brown says:

    Hey,

    thank you for sharing this. Is it possible to call with the background-function anything you want? Can I play a remote sound even if the phone is locked? I wanna make an alarm clock that plays a remote sound file even if the screen is locked. The alarm should fire a local mp3 file or a audio stream from server. Is that possible?

    Thank you.

    Jack

    • Drew says:

      Hi Jack,
      You can attach a sound to the notification but it must live within your app, no streaming. Also because iOS limits what an app can do in the background you cannot take more action beyond notifying the user – if the user opens your app from the notification you are free to do anything with that, that’s what the Background and Foreground functions are for!

      • Jack Brown says:

        Ok, that means the background callback is only the function fired when opening the app from LocalNotification. The foreground callback is what is called when the app is open und the Notification would be fired. Is that right?

        Is there no way to create a normal alarm clock? How do these guys solve it in their app? http://itunes.apple.com/de/app/progressive-alarm-clock-for/id366043848?mt=8

        I know that LocalNotification is only playing that limited 30s .caf-file. But what about AVAudioPlayer?Can I call that at a specific time?

        Thank you for your response.

  22. José says:

    Hi and thanks again for the tutorial. I have been trying to get the plugin to work on ios with cordova 2.2.0 but it doesn’t work. Is there something I am doing wrong? Does it work with cordova 2.2.0
    thanks
    José

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>