What is the best way to set up local notifications on iOS using custom UIPickerViews?
This blog looks at how we can use selected picker options to set the local notification to the required period. In a previous blog, I introduced a recent project where we wanted a user to be able to set up a periodic reminder from a set of different periods to start on a specific day or date depending on the period and walked through; how we populated the UIPickerViews using enums, how we could link both pickers using enum associated values and how we could handle the selection of each picker using generic picker data sources. This blog is the second part of that guide.
User notifications on iOS come in 2 flavours. Push (or remote) notifications are generated from a server and use Apple Push Notification service (APNs) to deliver notification to the user, or local notification, which are created by an app on device to show the notification when a specific condition is met (at a specific time or location).
The User Notification framework is used to set up and configure both types of notification. This blog focuses on the local notifications. The feature we want to build is to let the user set up a periodic reminder. This could be achieved using push notifications, making an API call to our server to schedule the notification and have the service run a task periodically to send a push notification back to the user's device at the required time. This implementation, however, is far more complex and would require many more moving parts, building and maintaining a server (if not already needed by the app), adding a new API endpoint, adding the APNs capability to the app, setting up scheduled tasks in the server to send messages out for each user that wants to receive notifications. We can use local notifications to simplify all this and not require external servers.
The first step in creating notifications is to request permission from the user to send them notifications.
We get the current User Notification center, this object manages all the notification related activities for the app. We then request authorization to send notifications with a set of options describing the type of notifications you want to send, here we are requesting to display an alert, play a sound and update the app's badge. If the permission is granted we can continue with setting up our reminder notifications, (any errors should be handled appropriately).
The code for setting up a local notification is fairly simple, there are three parts to a local notification, the content, trigger and request.
The content is what is actually shown to the user, text, sound, badge number, attachments (for rich content such as images) and user info that might be used when interacting with the notification. For the reminder notifications we are using we will only be setting the text of the notification. A basic notification content could be:
The trigger is the condition for delivering the notification, there are 3 types of trigger that can be used. A UNCalendarNotification, used to delivery the notification on a specified date and time, a UNTimeIntervalNotificationTrigger used to schedule the notification after a specific number of seconds, a UNLocationNotificationTrigger is used to delivery a notification when the device enters or leaves a specified geographic region. All notification triggers can be set to be repeatable in which case the notification trigger will be rescheduled again when the notification is delivered. Examples of the different types of trigger:
The request is created from the content and the trigger and will then be passed to the notification center for delivery at the appropriate time. The request also takes an identifier string which can be used to uniquely identify the specific notification. This can be useful for removing a specific scheduled notification or updating a notification with new details. If a new notification is required each time the identifier can just be set with a generated UUID string. Here is how you set the notification request:
For our use case we want to set up a repeating notification on a certain date or day of the week with a period of weekly, monthly or quarterly. For this use case the UNCalendarNotification trigger best suits our needs as the `dateMatching` initializer takes a DateComponents object which allows us to specify different specific units of date time such as day of the month or day of the week. To set a repeating notification every Monday morning would require a trigger like this:
To set a recurring trigger on the 5th day of each month we can use:
To set up quarterly reminders required a slightly different use of the UNCalendarNotificationTrigger, as there is no specific quarterly DateComponents value. We want to allow the user to select a quarterly period starting on the first day of the first, second or third month within a quarter, i.e if they want a quarterly reminder on the 1st month of the quarter they'll get a reminder on the 1st of January, April, July and October, the 2nd month of the quarter would be, February, May, August and November, the 3rd month of the quarter would be March, June, September and December. As we can't create a quarterly DateComponents object we can instead create 4 separate repeating triggers on the 1st day of a specific month and as they are repeating each one will repeat once a year on the 1st of the specified month.
Now we have all the required components to schedule a notification for a specified day or date, we can tie in our generic UIPickerViews to pick the correct values to use for our DateComponents. Looking back at the previous block post, all we need to do is assign these DateComponents to each of the FrequencyOption enums and their associated type enums (WeekOption, MonthOption and QuarterOption), bearing in mind that the QuarterOption needs to return 4 DateComponents.
We can add a dateComponents variable to each of the enums which will return the set of DateComponents for the specific enum case. The top level FrequencyOption enum will return it's associated types dateComponents. In order to create the DateComponent for each case we can make the enum store a raw Int value that will be used to initialize the DateComponent. The resulting enums will look like this:
With these updated enums, the UIPickerViews created previously will now contain the DateComponents for every combination of FrequencyOption and associated value options. In the UIPickerView delegate for the associated value enum we can trigger the creation of a scheduled notification.
There are plenty of extensions to this work. Further frequency options could be added such as a yearly or 6 monthly option. All you would need to do is add a new case to the FrequencyOption and create a new associated enum type for the new option with the required set of rawValues and DateComponents.
Even though the notifications are scheduled so will persist on app close, when the app reopens we may want to display the previously selected option so they can change the notifications to a different period. We would then need to persist the selection using something like UserDefaults. To do this we just need to make the enums conform to the Codable protocol so we can encode the selection using a JSONEncoder.
Search over 300 blog posts from our team
Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!