WWDC2010 Session 129

Transcript

>> I'm Jacob and I'm going to be joined by my
colleagues Chris and Darryl to talk to you today
about implementing push and local notifications.
So there's a bit of dilemma with iOS apps here.
Your app can't always be running when you need it to run
but you might have an important message
that you need to get to the user.
For example, your game might want to tell the user that
there're crops ready on their farm that they need to pick;
or, I know you guys all love that game, your baseball
game is about to start and you want to watch it;
or they might have gotten a message
from their friend on your service.
How do you get these messages to
the user if your app isn't running?
The solution on iOS is to use notifications.
There's three different styles of notifications on iOS.
The first is that you can set a badge.
The badge lets the user know that there's
a little bit of data waiting for them
in your app. The second style is an alert.
You can pop up an alert or send a short message
to the user. The third style is a sound.
You can play a sound that will get the user's attention.
Note that one notification can contain any
combination of these three styles of notifications,
so you can play a sound and set a badge at the same time.
We have two ways of creating notifications on iOS.
In iPhone OS 3 we introduced push notifications.
Push notifications are a way for your server
to get a notification onto the device.
It does this by talking to the APNs Apple Push
Notification Servers who forward the message onto the phone
through a connection that they keep open all the time.
In iOS 4 we've introduced local notifications.
Local notifications are a way for you to create these
same notifications but without the network connection.
They're similar in a couple of ways.
The first is that they look the same to
the user no matter how you create them.
If you create the notification through push
or locally, they're going to appear the same.
You have a badge, you can display
an alert, or you can play a sound.
Also in both cases, the iPhone OS
is acting on behalf of your app.
Your app doesn't need to be running
for a notification to happen.
There are some differences between
push and local notifications however.
The biggest is that push notifications come from a
server while local notifications are created by your app.
This means push notifications are much better
suited for any sort of network-based service.
You can keep the user connected to a service through
push notifications even if your app isn't running.
With local notifications they're scheduled so they're
much better for any sort of time-based notification.
Also local notifications can be set to repeat so they can
occur every day or every week indefinitely into the future.
With push notifications it's a single shot deal.
The server sends one notification
and it appears on the phone.
So why would you want to use notifications?
Well they're a great way to get the user's attention.
It's standard behavior on the OS
and users are used to these.
It's also a great way to improve the battery life.
Since your app doesn't need to be running you aren't
using up any CPU and the battery life improves.
Finally, push notifications are a great way for
your app to stay connected with a network service.
Your app doesn't need to stay running or keep
a network connection open to your servers.
The phone iOS will do it on behalf of your app.
So now Darryl's going to come up and talk to you guys
about how to implement push notifications on the phone.
>> Hi my name is Darryl.
I'm one of the engineers that works on the
service which allows you to send notifications
from your server directly to an
application running on the device.
So just a few things I'm going to talk about today.
One of them is just to give you a brief overview
of how the notification architecture works.
After that I'm going to tell you a little bit about the
things we've been working on and how you can make use
of them to provide an even better experience for your users.
So first of all: How the Push Notification System Works.
Now if you're already using the Push
Notification Service, this is just going to serve
as a brief reacquaintance to how it works.
If you haven't used it, then this will
be a bit of an introduction for you.
So to make use of the service you require two things.
You require a device with your application running on it
and you require a server which will send notifications.
Once you have those two things you're ready
to make use of the Push Notification Service.
So just any particular device you
need to know that device is Token.
This Token is available to you by the APIs on the device.
Once you have that token you would tell your server about
it and when your server has a Payload ready to send,
you combine the Token and Payload together into a
notification and send it to the Push Notification Service.
From there, the service will route
the notification as appropriate.
Let's take a closer look at that communication
between your server and the Push Notification Service.
The protocol used here is a Binary Protocol
and it consists of a couple of things.
There's the Device Token, there's the
Payload which is specified in JSON
and there's the Command Identifier
so in this case it's simply 0.
The prefix to the Token and the
Payload are their respective lengths.
This makes up the entirety of a notification.
All right some considerations for sending notifications.
First of all the system was designed
entirely with performance in mind.
As such the communication with
the service is unidirectional.
It's also a streaming protocol which means
that you'll send multiple communications back
to back over the same connection.
It also features a store and forward mechanism
such that notifications which are sent
to devices not currently online will be
redelivered when that device reconnects.
The system also presents some debugging challenges.
And this is mostly due to the fact that the
system terminates connections on invalid data.
Let's take a bit of a look at how that becomes a challenge--
when your server establishes a connection with APNs
and notifications flow over that connection.
Let's take a closer look at what is
actually happening on your server.
We can actually think about that as your service which
communicates to the operating system running your service.
When you have a notification ready to send, you're
actually sending that to the operating system
and it's up to the operating system to
manage the particulars of that connection.
If we have multiple notifications in flight
and there is a problem with a notification,
then the push service will simply terminate the connection.
This can lead us to an issue where we have an orphan
notification and it's a challenge for your service
to do something intelligent in that case.
As such we have an enhanced Binary Interface
which will provide you with increased feedback.
Along the way we've preserved what's best.
This is still a high performance protocol.
It maintains the same streaming capability but
now we will provide you with concise feedback.
This is not a pure request response
system as that would impair performance.
Rather we've designed this system to provide you
a response only in the case of an air condition.
So let's take a look at how that would work.
In the same case as we had before, your
service has two notifications to send.
We still send those to the operating system but if
you'll notice, we are keeping track of a sliding window
of notifications we have sent as well as
we have an ID associated with each one.
In this case to keep things simple we have ID 1 and 2.
Now when those notifications are sent if there's
an issue with any particular notification,
before disconnecting the connection, the Push Notification
Service will send you a response which contains
that notification along with a little bit of information
to tell you what was wrong with the notification.
From there, you can know that there was
an issue with that particular notification
but the notifications sent subsequent
to it still need to be redelivered.
We're going to talk a little bit
about the Store and Forward mechanism.
As mentioned the Push Notification System accepts
notifications for devices which are not currently online.
And will store them for a limited amount of time
and redeliver them when a device reconnects.
However, this is potentially problematic as the information
contained within any notification may be sensitive to time;
and yet we will deliver it potentially after
too much time has passed even perhaps after days
after the information in the notification was useful.
When you can imagine having, say, a restaurant
application that lets you know when your table is ready,
if you turn off your device it's not useful for you to
receive information that your table is ready tomorrow.
And so with the Enhanced Binary Interface,
you can now specify a maximum useful
lifetime for any particular notification.
And if a device reconnects after that expired period
has passed, that notification will not be delivered.
So note that we will always attempt to
deliver a notification at least one time.
So you can make use of this for a
fire and forget type notification
by simply setting the date to a time in the past or to 0.
Also note that while you are specifying
a lifetime, you'll still not be able
to exceed the maximum default expiry
for the Push Notification System.
And some further considerations for
the expiry, it's specified in seconds
since epoch, this is the number
of seconds since January 1, 1970.
You can still make use of the existing message command which
essentially is the same as using the command with the expiry
and saying please store this as long as you can.
Also note that this system also expects time coordination on
your server; so you want to make sure that you're making use
of the Network Time Protocol so that your server time
is in sync with the Apple Push Notification Server time;
so putting that altogether the existing message
command as we've looked at looks something like this.
There's the Device Token, the Payload, the
Command Identifier and the Prefix Links.
To make use of the enhanced Binary
Interface we change a few things.
First of all it's a new command
ID and instead of 0 it's now 1.
You will then add in your particular
identifier and your expiry time.
This makes up the entirety of the
enhanced Binary Interface Protocol.
The last thing I have to talk to you about today
is receiving notifications over a Wi-Fi connection.
As some you have noticed, prior to iOS 4 devices
which were connected by a Wi-Fi and sleeping resorted
to a polling behavior in order to receive notifications.
However, with iOS 4 we now fully support
push notifications over Wi-Fi connections.
This requires an iPod, iPad or iPhone 3GS or newer.
So in summary we have the Enhanced Binary Interface
which will alleviate some unknown disconnection issues
for developers as well as allowing you control
over the lifetime in any particular notification.
Also all of this is available right now.
You can use this new message command as soon as you exit.
As well we have Enhanced Device Support such
that we now support push notifications over Wi-Fi
with iOS 4 on iPod, iPad and iPhone 3GS or newer.
All right that's all I have to talk to you about today.
Back to Jacob to tell you more about local notifications.
>> Jacob: Great.
Thanks Darryl.
So now I'm going to talk to you guys about
implementing local notifications in your app.
Just to recap real quick local
notifications are new to iOS 4.
They're scheduled to occur at some point in the
future and you can also mark them as repeating.
You can also cancel your local notification at any time.
So when would you want to use a local notification?
Let's go over some examples of apps that might use them.
The first is a Alarm Clock app.
You want an alarm to happen every morning at the same time.
This is a great example of a repeating notification
because you want it to happen every day of the week.
Another example is a Reminder App.
You want to be reminded to not
forget something before a big trip.
The last example is a Location Based App.
Using the new location services in iOS 4
you can provide location in the background.
If your background app figures something out like
your friend is near, it can use a local notification
to display an alert and let the user know.
There are some times, however, when we
don't want you to use local notifications.
The biggest is for any sort of in-app
errors or confirmation dialogues.
For that it's best to use UIAlertView or
just make your own feedback in your own UI.
Also if you want to alert the user about a calendar-based
event, we recommend that you look at using EventKit.
It will allow you to create an event in the user's
calendar and you can set an alarm on that event.
The user will get notified by the system
when that event is about to occur.
So now let's take a look at the API for UILocalNotification.
If you remember there's three different styles
of notifications-- a badge, an alert and a sound.
So we have properties for each of those.
Once you've set that up you're going to want to schedule
your notification to happen at some point in the future
and you may also want to mark it as repeating.
Notifications also provide a userInfo Dictionary so you
can add any extra metadata that your app might need.
So let's start by taking a look at the three
different styles you can set on a notification.
The first is a badge.
If you set the applicationIconBadgeNumber property when
the notification fires, you'll get a badge on your icon.
There is one trick to this though.
If you set zero your badge won't get cleared.
To clear your badge or to change it
once your app is running you'll want
to use UIApplications applicationIconBadge property.
The next style of notification is an alert.
If you set an alert string as an alertBody in your
notification, an alert will pop up with a single button.
When the user taps that button the alert will be dismissed.
You can also set actions on your alerts.
What actions do is allow the user to tap
on that extra button on the notification
to launch directly into your app - just like that.
So there's one note here.
Since you're displaying strings to the user,
it's always a good idea to localize them.
Even if your app isn't currently
shipping in multiple countries,
it will make your life a lot easier if
you decide to do that in the future.
We've made this really easy to do on iOS 4.
Just create a localized strings file and add key
value pairs of the strings that you want localized.
When you make your alert use those
keys as the content of the alert.
Note that we are not localizing the strings right now.
This is important because if the user
changes their language between now
and when the alert fires, they'll
get the right localized alert.
The OS will localize it on behalf when the alert fires.
The last property for alerts is a launch Image.
This is new to iOS for both push and local notifications.
So those of you that aren't even planning on doing
local notifications might be interested in this.
When an alert fires and the user taps on that
button, your app is going to get launched
as if they had tapped on the icon from the home screen.
This means you'll see the default launch image or you
may see a screenshot of your app in its previous state.
This might not always be what you want though.
You might want to take the user directly
into the thing you notified them for.
To do this, you set an alertLaunchImage.
In the case of our example app here we're telling the
user about a baseball game that they want to watch.
If they hit the view button, we want
to take them directly into the view
where we're showing them the baseball game in progress.
So we take a screenshot of that view
and we set that as our alertLaunchImage.
When the user taps the button the app will be
launched and the transition will be seamless
between the application launching and your view coming up.
Finally, the last style of notification
you can create is a sound [glass chime].
You can play any sound in your apps bundle or if you
don't have your own sound you can just play the system
default sound.
If the sound is working it sounds
like this [background sound].
Cool. That's it for the different
styles of local notifications.
Now you're going to want to schedule that
notification so let's look at how you do that.
To schedule a notification you just set the fire
date and time zone property of the notification.
There's one catch though.
Try not to do your own time-based math here.
This time tomorrow isn't necessarily going to be sixty
seconds times 60 minutes times 24 hours from now.
If say Daylight Savings Time happened tonight you'd
end up an hour off tomorrow when your alert fired.
Instead, you should use NSDateComponents to set the
specific date you want the notification to fire at.
Then the OS can handle all the
tricky time-based math for you.
So I want to take a minute to step aside
and talk about the different kinds of times
that you can set a notification to fire at.
This gets a little tricky but hopefully
we can figure it out.
There are two different kinds of times.
The first is what I'm going to call a Universal Time.
A Universal Time happens at the same instant
no matter where you are in the world.
It doesn't have anything to do
with the time zone you are in.
A Wall Time is whatever is on the clock
on the wall where you are right now.
So let's look at a Universal Time first.
To make a notification with the Universal
Time, just set the date and no time zone.
When you do that the notification will happen
at the same instant all around the world.
A great example of this is a baseball game.
Baseball games start at the same time whether you're in
San Francisco or in New York so we just set an NSDate
on this notification and when the notification
fires the time in San Francisco may be 2PM
but at the same instant it's going to be 5PM in New York.
The other kind of time is a Wall Time.
A Wall Time has a date and a time zone set on it.
This time is going to get changed
depending on what time zone you move to.
So an example of this is an Alarm
Clock that wakes you up every morning.
You always want that to happen at the same time so
you set a date and a time zone on that notification.
Now when you're in San Francisco
for WWDC this week it happens at 9AM
and when you move back to New York it still happens at 9AM.
So just to review that-- if you don't
have a time zone it's a Universal Time.
A couple examples of this are a
Baseball game or a Conference call
or an event like the close of the Stock Market.
These all happen in a single instance
regardless of what time zone you are in.
The other kind of time is a Wall Time.
A couple examples of this are an Alarm Clock, or New Year's
Eve or a TV show that happens at the same time every day.
All right getting back to scheduling notifications.
You can also mark your notifications
to repeat at certain intervals.
To do this just set the repeatInterval on your notification.
You can use any NSCalendarUnit to do that.
So in this example we're using NSDayCalendarUnit
and the notification will repeat every day.
But you could also use NSWeekCalendarUnit;
now it repeats every week.
One thing to note here is the repeatCalendar
that's also associated with it.
Not all users use the Gregorian calendar, so if you're doing
something that assumes that a year is a certain length,
you want to also set the calendar that you're
expecting otherwise you might get the wrong repeat
for a user using a different calendar.
So that's how you schedule a notification.
The last property in a local notification is the userInfo.
The userInfo is just an NSDictionary that you can store
any additional keys that your application might need.
A couple examples of what you might want to put here an
identifier for the notification or possibly some information
for your app of what view it should launch into.
You can put anything in here that
can be stored in an NSDictionary.
All right so we now have a local notification and we
want to get that to actually appear on the system.
How do we do that?
Well it's really simple.
Just call schedule notification on UIApplication.
There's also a matching cancel call.
We've also provided a couple of convenience methods
for you so you can get all your local notifications
and you can cancel them all with one call.
Finally, UILocalNotifications implement
the NSCoding Protocol
so you can serialize them out to
your database and use them later.
Now let's look at the different
cases where your notification fires.
There are three different possible states for
your application when the notification happens.
You could be not running or suspended, your app could be
in the foreground or you could be one of the special types
of apps that runs in the background
like a location or audio-based app.
So let's look at the first case where
your app has never run or it's suspended.
When the notification fires, iOS will
handle that notification on your behalf.
The notification will pop up and if the user
decides to tap on that button and launch your app,
your app will get brought to the foreground.
When your app launches you'll get the
application:didFinishLaunchingWithOptionsCallback
and the notification will be in there.
Your app can then do whatever it needs
to do to respond to that notification.
The next case is an app that's
already running in the foreground.
In this case the notification will still come in
but the iOS won't do anything on behalf of your app.
No alert will pop up or no badge will be set.
Instead we know that your app is going to be
smarter about displaying the appropriate UI
or just going straight to the view that you need to go to.
So in this case the notification comes in
and your application receives
application:didReceiveLocalNotification.
Your application can then do the appropriate thing.
In this case we want to display a subtle little
reminder about the baseball game that's going on.
Finally, there's the third case where
your app is running in the background.
If you're one of the applications using the new
multitasking API in iOS 4 you may be running
in the background for location, Voice Over IP, or Audio.
If you are one of these apps and something
causes you to want to alert the user,
you'll want to schedule a local
notification with an action button.
This is a good way for the user to be given
the option to jump straight into your app.
So you can schedule a local notification now
rather than scheduling it for in the future.
This gives the user the ability to launch your app.
Note however that if you aren't one of these special
background apps, this probably isn't what you want.
If you call scheduleLocalNotificationNow and you're already
in the foreground, the system is just going to turn right
around and call didReceiveLocalNotification
and nothing's going to happen.
All right so a quick word on alerts.
Be wise with your alerts.
Don't bother the user.
We'd hate for them to delete your app because you
displayed too many alerts and you bothered them.
With push alerts they can also
just turn off push for your app
and then you can't get any information
to them so be nice to the users.
If you can, use a badge instead of an alert.
It still lets the user know that there's information
there for them, but it's a little less intrusive.
All right so now Chris is going to come up
and give us a demo of what we just learned.
>> Hi I'm Chris Marcellino.
I'm one of the iPhone iOS Software Engineers and
I'm going to talk to you and show you a few tips
about some UILocalNotification coding in your apps.
If you were here last year you might
remember that we showed you the Squawkr app
and we showed you how to extend
it to use push notifications.
Well this year I'm going to show you how
to extend it to use local notifications.
So Squawkr is your standard social networking app
except it's an app that's all about complaining.
As you see here I'm complaining about my bad day
and my lunch and other things that occurred to me.
I have some of my friends complaining
about stuff going on in their lives too.
So I took the Squawkr app and I
decided that I really wanted the ability
to let users say they want to be reminded when to Squawk.
So I added a button that brings up a view controller,
lets the user pick just really simply how
far in the future they want to be reminded.
And what I did is I wired up this
Schedule button to the schedule IV action
in the schedule view controller that runs to this code.
So say once I click Schedule this action
is going to get called and first I'm going
to cancel all the existing notifications just because
my app only wants one notification active at once.
And I'm going to get the countdown duration from the date
picker and then speed it up a little bit for purposes
of this demo; and then finally I'm
going to create my local notification.
Now this local notification doesn't use any calendar.
It's just a simple time in the future.
So I create a new NSDate with the time interval
nSeconds in the future and set that as the fireDate
on the UILocalNotification and then I get my
localized strings from my alertBody and alertAction.
And as Jacob talked about these are strings that are
in my Localizable.strings file in my apps bundle.
So here's my English strings and you of course could have
strings for every language that you're using or supporting;
and then I have a custom sound here that
I want to set as my soundName so we hear
that when the switch is on sound on the phone.
And finally I have a custom launch image because when I'm
launching the app for the user to create a new Squawk I want
to make sure the UI doesn't glitch that it goes straight
from the right default PNG showing the creation UI seamlessly
to the UI without showing the old snapshot beforehand.
And finally, I add in a little bit
of user info to give me some context
about what this local notification
was for when the app's resuming.
So I just put in an NS Boolean Bool:Yes with
the createSquawk key and I'll look in a second.
And of course I schedule it, release it and
then dismiss view controller [sound effects].
Okay, so let's look at the application
delegate of my application.
So in my SquawkrAppDelegate class, I
have two methods I've added code for.
I've added application didFinishLaunchingWithOptions,
it's a delegate callback, your application gets on launch
to implement it; and I also have
application didReceiveLocalNotification.
So let's say that I schedule a notification
and quit the app [sound effects].
Now the app wasn't running as you saw
that I terminated it in the App Switcher.
But here I launched the app it's going to launch fresh
and the application didFinishLaunchingOptions
delegate callback is going to get called.
So in my application didFinishLaunchingWithOptions
delegate callback,
what you see here is that I get the local notification
out of the launch dictionary, and I check to see
if the userInfo key has my createSquawk key.
If it does then I'm going to go
straight to my Squawk creation UI.
And you'll notice that you saw nothing but a seamless
transition from my default PNG to my Squawk UI.
Now let me show you another case.
If, for example, instead of launching fresh, my application is
running if it supports multitasking it's linked on iOS 4.0,
when I schedule a notification and spin the app, once
the Squawk fires [sound effect] and I resume the app,
you're going to see
that my application didReceiveLocalNotification
delegate callback is going to get called.
And I'm stuck here in the debugger
we're broken, this is the default PNG
that looks just like my UI that I'm going to launch to.
But that's actually the image itself-- not real UI.
I took a screenshot of my app when I was developing it.
So here I am in this delegate callback
didReceiveLocalNotification,
and one thing that you may have encountered if
you've already started using local notifications
in the developer seed, is that you want to know sometimes
if your app is structured this way whether you're resuming
in response to a local notification or if you've
just received a local notification while your app was
already foregrounded.
The way to tell apart those two situations is
to call the UIApplication ApplicationState
property and get the current application state.
So the application state has three
return values that you can get.
You can get active, inactive or background; and the only
time you're going to get this delegate notification call is
when either you're active already or you're inactive.
Now if you get this callback and application state
returns inactive, that means the user launched your app
with the action button in your notification and the
application is resuming a response to the notification.
So if your UI was to normally maybe ask user confirmation
while the app was already foregrounded you might not want
to do that if the app is resuming because the user already
acknowledged your notification and indicated they want
to launch your app you go straight to that UI.
So that's how you can tell the
difference between those two cases.
Again I checked my createSquawk key in case I had multiple
notifications and wanted to differentiate between each
of them; and in this case when I have
state UIApplicationStateInactive
and I have shouldSquawk, we launched right to the Squawk UI.
As you see here I'm right in the Squawk
UI and I can create a new Squawk.
So another thing I'd like to note is if you receive a
notification while your application is already running
foregrounded, one thing you should do is not
just if you can help it display a UIAlertView.
Try to do something custom like take the
user to the right part of your application
or have your existing custom UI reflect
the fact that local notification fired.
So one other thing you may have noticed is that when I
cancelled all notifications here I wasn't really specific
about which one I wanted to cancel or anything.
But if you do start cancelling specific
notifications or maybe you have one
of those background apps that's using notification services
or audio, and you're targeting certain notifications
to cancel, one thing you should keep in mind is
that if a notification alert is already up on screen
and you cancel it, iOS will tear down the
existing already visible notification alert
so the user will kind of maybe wonder why it disappeared.
This might be useful if you have notification
data that's no longer valid and you want
to actually make it go away and
maybe replace it with a new one.
But if you don't and say the notifications
just fired, don't bother cancelling it
as the operating system will cancel it for you.
The final note I want to make on UILocalNotification
is that as Jacob mentioned it's for copying and coding.
Coding gives you a lot of power in that
your application can serialize instances
of UILocalNotification using NSKeyArchiver
and NSKeyUnarchiver and store those
in any existing data model you might have
like a core disk or a database on disk.
When you restore those local notifications
when you re-create them using Key Unarchiver,
you can compare them using is equal to the existing
ones you have running via scheduled local notifications
or any other ones you might have in your model or even
make copies of those and change them to suit your needs.
One consequence of this is that UILocalNotification
implements is equal on hatch in the deep sense.
Two different instances of UILocalNotification will be
equal if all of their properties are the same so you can use
that to compare an instance you may have gotten from
the system versus one you had in your data store
or one you created by hand to see if they're equal.
Equivalently you can ask the system to cancel notifications
where you've unarchived an instance from your date store.
If you change any property of the notification, you're
going to find your notifications are no longer equal.
And of course the system implementing has correctly on
UILocalNotification means you can use your own dictionaries
and your own sets to be able to
correlate and find your notifications.
That's all I have for you today.
I'm going to hand it back to Jacob
for more on local notifications.
>> Jacob Farkas: All right thanks Chris.
So in summary, local notifications and push
notifications are great ways to get the user's attention.
It gives you all the functionality of being a background
app without you actually having to use any CPU.
This saves on battery life and users like it.
If you want to stay connected with a network
service, think about using push notifications.
Your server can send messages to the phone.
If you want to notify the user about something
that's time-based, use local notifications.
Remember to always localize.
It will save you a lot of work if you
ever decide to ship in other countries.
Also, remember the difference between
Universal Times and Wall Times.
notifications can fire at different
instances depending on how you set them up.
And finally, if your app is already in the
foreground no UI is going to be shown on your behalf.
You should handle that notification and
do something appropriate for your app.
If you'd like any more information on
this Mark Malone is our Evangelist.
We also have information up on the forms.
There's some related sessions you might be interested
in; the Multitasking session will tell you all
about being a background app which
you can use local notifications with;
and there's an EventKit Session later today.