Local Notifications are scheduled and fired inside the app. If a user adds a new task to his To-Do list, we can remind him of it. The mechanism is very easy - we create notification object, we set its date and text, and iOS will show it when the time will come.
But before showing any notification to the user, we first need to ask
his permission to do it. You can ask for it whenever you want - some
people add this code to their
App Delegate
,
so app asks user right on app's first launch. However it would be a
better idea
to tell the user why and when notifications will be used, and only then
show them notification permissions dialog.
Since permissions dialog can't be modified, you can also show a friendly warning that iOS will show Notifications Dialog now. Also, if the user did not allow the app to show him notifications once, we won't be able to display the dialog later. The friendly warning can be shown and declined many times since it is not a real permissions dialog.
In the superapp
, we are going to show local notifications three
times
per day. We want to remind the user to track his mood in the morning,
in the afternoon, and in the evening.
To be polite, we will ask the user if he is fine with allowing us
to send notifications. If it is fine, we will show him iOS Notifications
Permissions dialog, and will never show him these dialogs again.
If it is not - we will show this dialog next time
the user loads the app. Let's do it for now in
HomeScreen
's
on_load
:
class HomeScreen < PM::Screen
title "Home"
tab_bar_item title: "Home", item: "images/home"
def on_load
self.view.backgroundColor = UIColor.yellowColor
check_local_notifications
end
def check_local_notifications
# get current app notifications settings:
app = UIApplication.sharedApplication
notification_types = app.currentUserNotificationSettings.types
# check if user allowed us to send him notifications
not_allowed = notification_types == UIUserNotificationTypeNone
permanently_disabled = NSUserDefaults["notifications_disabled"]
if not_allowed && permanently_disabled != true
UIAlertController.alert(self, "Notification Permissions",
message: "We would like to remind you to track your mood. To do it, we need your permission to send you notifications.",
buttons: ["Don't allow", "Allow"]) do |tapped_button|
if tapped_button == "Allow"
# what kind of notifications we want to show:
types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert
settings = UIUserNotificationSettings.settingsForTypes types, categories: nil
app.registerUserNotificationSettings settings
end
end
end
end
end
Unfortunately, not_allowed
variable can be true in 2 cases: the user did not
yet
allowed us to show him notifications, or user has
denied
us to show him notifications.
Unfortunately there is no straightforward way
to make sure
whether user denied notifications or app did not even asked
for them yet.
If the user has denied notifications, iOS will never show permissions dialog again, and we will show our custom warning for nothing - even if the user taps "Allow", iOS won't show this dialog again.
To determine whether user denied notifications we use
NSUserDefaults
- a place where we can persist some data.
registerUserNotificationSettings
method
will call a delegate method in the
AppDelegate
:
application(app, didRegisterUserNotificationSettings: settings)
settings.types
will be equal
UIUserNotificationTypeNone
. This info will be enough
to understand that Notifications were denied, and we will save this
state in
NSUserDefaults["notifications_disabled"]
:
def application(app, didRegisterUserNotificationSettings: settings)
if settings.types == UIUserNotificationTypeNone
# user did not allow us to show notifications:
NSUserDefaults["notifications_disabled"] = true
else
NSUserDefaults["notifications_disabled"] = false
end
end
Now when we finally can show notifications, let's talk how they work with the app in different states.
When your app is in the foreground (user currently uses it), and
notification arrives,
AppDelegate
will fire a method
application(app, didReceiveLocalNotification: notification)
.
There you can tell the app what should it do. When the app is in
the foreground, no sound or banners will be shown, and badges won't be
updated.
If device's screen is turned off at the moment when a notification arrives, iOS will show the notification with your text and app's icon on its Lock Screen.
If the user has another app opened at the moment when a notification arrives, iOS will show a banner at the top of the screen with your notification's text.
Let's make our
HomeScreen
create notifications for next three days. We will do it only if user has
notifications enabled:
def check_local_notifications
app = UIApplication.sharedApplication
notification_types = app.currentUserNotificationSettings.types
not_allowed = notification_types == UIUserNotificationTypeNone
allow_tapped = NSUserDefaults["allow_tapped"]
if not_allowed && !allow_tapped
# code to show dialog
else
# set new notifications for next 3 days, including today:
now = NSDate.now
date_components = { year: now.year, month: now.month }
hours = [9, 14, 20]
# 3 days, 3 notifications per day:
3.times do |i|
day = NSDate.now + i.day
date_components[:day] = day.day
hours.each do |hour|
date_components[:hour] = hour
date = NSDate.from_components date_components
create_notification(date)
end
end
end
end
def create_notification(date)
# we don't want any notifications in the past:
return if date < NSDate.now
# check if this is a new notification:
app = UIApplication.sharedApplication
notifications = app.scheduledLocalNotifications
same_date_notification = notifications.find do |existed_notification|
existed_notification.fireDate == date
end
# if notification with same date exist, don't create a duplicate:
return if same_date_notification.is_a?(UILocalNotification)
notification = UILocalNotification.alloc.init
notification.fireDate = date
notification.alertBody = "Have you tracked your mood yet?"
notification.soundName = UILocalNotificationDefaultSoundName
notification.alertAction = "Track it!"
UIApplication.sharedApplication.scheduleLocalNotification(notification)
end
First we made sure that notifications can be scheduled. Then we got
current date components - we will use them to create a correct date
for a notification's fire date.
The hours
variable contains hours values when we want notifications to be
fired: at 9:00AM, 14:00 (2:00PM), and 20:00 (8:00PM).
When we have everything ready, we start a loop with
3.times do
. This command will iterate three times, and
i
variable inside of it will be equal to iteration index - 0, 1, or 2.
NSDate.now
returns an object with a current date and time.
i
variable is used to create a day in the future:
# when i == 0:
day = NSDate.now + i.day
# => day will be today, since today + 0.day is still today.
# when i == 1
day = NSDate.now + i.day
# => day will be tomorrow, since today + 1.day is tomorrow
# when i == 2
day = NSDate.now + i.day
# => day will be the day after tomorrow: today + 2.day
Having a correct day, we set it to the
date_components
hash, to create a date object from it. Then we iterate over
all hour values to set
:hour
value to the
date_components
.
And when we finally have complete date Hash, we pass it to
create_notification
method.
We could add the code for notification creation to the loop, but it
would make our code look heavier.
Method
create_notification
is used to create a notification for a given date.
Because we don't want to show
notification in the past, we compare current date and notification
date, and we stop method execution if notification's date is in the past
(notification's date is lower than current date).
Then we check already created notifications - if there is a notification that is going to fire on the same date and time as the new one, we also stop method execution because we don't want duplicated notifications to bother the user.
And then finally, we create and schedule the notification. We can set its
date, text, action text that will be shown on a lock screen, sound,
and badge number if needed. Then we tell the app to schedule
the new notification with app's
scheduleLocalNotification
method.
In order to test it, you could try to set the date to the next second or so with:
notification.fireDate = NSDate.alloc.initWithTimeIntervalSinceNow(3)
and check how does it work:
The only thing left is to make the app
process notifications when the app is in the foreground. There is no
action the we need to do when reminder notification has been fired
in the active app, so we will just print a message in the console. If
you remember, method's name that is being fired when notification
is received is
application(app, didReceiveLocalNotification: notification)
.
Let's add it, and post notification's info to the console:
def application(app, didReceiveLocalNotification: notification)
NSLog "Notification received: #{notification.alertBody}"
end
We now have notifications working, so let's make a commit:
git add .
git commit -m "Local Notifications added"
In this chapter, we learned how to send local notifications. This type of notifications can be scheduled and fired right from the app. You can use it to remind a user about some events in his calendar, or about his tasks in to-do lists.
Before sending notifications, we need to ask the user for his permission. Be polite, and tell the user why do you need it - it will increase your chances.
To schedule a notification, create a
UILocalNotification
object first, and then set its
text, date, etc. Don't forget to pass it to the app so
it will be scheduled!
More info on local notifications can be found at Apple's iOS Developer Library.