Rich iOS notifications with custom input interface

Juraj Selanec
Undabot
Published in
8 min readDec 18, 2017

--

or how Jimmy won a new Iphone X

Who is Jimmy?

Once not long ago there was a young programmer named Jimmy who stumbled upon a simple but interesting app on the App Store. At first sight UndaQuiz looked like any other quiz app but after reading its description and weirdly good reviews on AppStore, he found out that this quiz is in fact a competition where the winner gets a brand new iPhone X. Jimmy, a huge Apple fan, didn’t have to think much, he immediately downloaded the app and signed in for the competition. After two weeks of answering all kind of questions, Jimmy made it to the finals! There was only one question separating him from the main prize! But for this question there was a special rule. Each participant will get a push notification at the same time and the one who submits the correct answer first, gets a new shiny iPhone.

Jimmy was very nervous on the day of the finals, he carried his iPhone with him everywhere and anxiously waited for the sound of notification. He was just about to finish his eighth coffee that day when he heard a well-known sound of notification. While unlocking the phone, his pulse suddenly increased so high that he accidentally deep-pressed the notification and discovered a secret, game changing feature. UndaQuiz had a possibility to quickly answer the question from a notification, which gave him the advantage over his competitors and led him to victory and a new iPhone X!

Jimmy really enjoyed using his iPhone but one thing bothered him so much that he couldn’t sleep at night — How did they make that notification?

Let’s be good friends to Jimmy and show him how he can recreate that notification.

Where to start?

Last year, on WWDC 16, as part of iOS 10, Apple revealed new UserNotifications framework. With the new framework developers gain power to create engaging notifications with completely customizable UI elements and custom interactions. Also, Apple provided a possibility to intercept and modify payload from the Apple Push Notification service (APNs) with data that doesn’t fit a rather small 4 kB remote notification payload. That brought all kinds of media attachments into the world of, until then, rather poor and simple notifications. Now, thanks to Apple (and all hardworking iOS developers out there in the world), we have images, videos and gifs coming to us through notifications of our favorite apps.

Apple didn’t stop there and this year they introduced one small but for user’s privacy very important feature for 3rd party apps — Hidden content. Nowadays, a great part of our daily communication goes through mobile applications, especially through notifications and many times they carry sensitive data that we don’t want to share. Apple realized that and introduced a new feature to once more enhance the security and privacy on their devices. With hidden content user can hide notification’s content when device is locked and replace it with custom placeholders.

Now when we know what we can do with notifications, let’s dig into the code.

Permissions and registration

First of all, if we want to show any kind of notification to the user, we need to acquire his permission and register for remote notifications. After successful registration we will get a specific device token from the system, which is then used to target the device with notifications from the APNs. Just keep in mind that you will need paid developers account for retrieving a device token and sending remote notifications.

For the purpose of this demo we will ask for permission after the app launches. To accomplish this, we need to call the following code from the application(_:didFinishLaunchingWithOptions:): method in AppDelegate.

In the code above we are requesting authorization for notifications that will display notification alert, play some sound and show a badge on an app icon. After that, we are registering for remote notifications. If everything goes well, we should get the device token through the application(_:didRegisterForRemoteNotificationsWithDeviceToken:) method and in case of an error, the system will call application(_:didFailToRegisterForRemoteNotificationsWithError:).

We acquired the permission to show notifications and we successfully registered for remote notifications. That means that we are ready for the next step.

Modify remote content

Earlier we said that in iOS 10 Apple introduced the possibility to modify content of the remote notification. Since we need to add videos and images to our notification, and that certainly doesn’t fit the payload, we will need to download the media separately and add it to the notification.

To download the media and present it in the notification, we need to add Notification Service Extension to our project. Xcode contains the template of Notification Service Extension target with a subclass of the UNNotificationServiceExtension class which provides all the necessary methods for customizing the content before it’s displayed to the user.

To add the extension, in Xcode, we need to go to File -> New -> Target and select Notification Service Extension. After we enter the name of the extension, Xcode will ask us to activate the extension. We want to activate this right away because we will soon use it for creating our notification.

Our template already overrides the didReceive(_:withContentHandler:) method that performs the main work of our extension. In that method we will make all the changes to the notification content. It’s important to keep in mind that this method will be called only for remote notifications with apps’ dictionary that include the `mutable-content` key with the value set to 1. Also, the method has limited amount of time to perform the download task and execute the provided completion block. If the method does not finish in time, the system will call the serviceExtensionTimeWillExpire() method to give us one last chance to submit the changes. If we do not update the notification content before time expires, the system will display the original content.

In the code we can see that we simply downloaded the media with our helper method and attached it to the notification. That’s all we needed to do in the Service Extension to prepare the content for presentation so let’s go and build our custom interface.

Custom interface

To show the custom interface of notification we need to use the Notification Content Extension.

To add the Content Extension we need to repeat the steps from previous section where we added the Service Extension, except this time we need to select the Notification Content Extension target. One can add more Content Extensions if they want to customize different notification categories. Each Content Extension handles only notification categories that are defined in its Info.plist file. In the Info.plist file under the UNNotificationExtensionCategory we can assign category identifiers that will be customized by extension. Also, all categories need to be declared in the app using the UNNotificationCategory and specified in corresponding push payload.

In the Info.plist there are two more values that we need to customize in order to recreate wanted notification.

Info.plist

The UNNotificationExtensionInitialContentSizeRatio is a value that represents the initial size of our view expressed as a ratio of its height to its width. It’s important to set the right value for this key because we want to have a nice and smooth initial animation of our view.

The UNNotificationExtensionDefaultContentHidden is a Boolean value that gives us the ability to hide default content view and show only our custom view.

The category is registered and we updated the Info.plist so it’s time to start building our interface.

The Content Extension created for us the NotificationViewController, a subclass of the UIViewController and the implementation of the UNNotificationContentExtension protocol. The UNNotificationContentExtension protocol provides the entry point for extension through the didReceive(_:) method which is called when the notification is received. The extension also created the MainInterface.storyboard where we can build the interface of view controller exactly like we would for any other view controller. The important thing to know is that the system prevents the delivery of touch events to notification view controller while it’s on screen, so we can’t rely on touch events in the interface.

To collect the input from the user we can use notification actions or we can build our custom input views (we will build them later on). Also, if our notification contains audio or video, we can use built in media buttons to support the playback of the media. All we have to do is implement the mediaPlayPauseButtonType property and return the type of button we want. Also we must set the frame of button through the mediaPlayPauseButtonFrame property and then collect user interactions in mediaPlay() and mediaPause() methods.

Desired notification uses overlay type of media button and we only need to implement the mediaPlay() method so we can play the video when user taps the media button.

As we said earlier, the didReceive(_:) method is called when notification is received and that’s also the place where we get the content of notification that we previously prepared in the Service Extension.

To make the notification attachments available for our app, we need to call the startAccessingSecurityScopedResource() and also the stopAccessingSecurityScopedResource() when we are done because all the attachments of notification are saved as a security scoped URL. After that, we simply customize the interface with content we received in notification.

Custom input view

The implementation of custom input for notification is simple and straightforward, as it is for any other view controller. To add custom input we just need to override three properties on the view controller and give them the right values. To give the view controller the ability for becoming the first responder, we override the canBecomeFirstResponder property, and for showing our custom views specially designed to collect specific user input, we override the inputView and inputAccessoryView properties.

The only thing that we need to do now is to show the input view when a video ends and we can easily do that just by calling becomeFirstResponder() on notification view controller.

The end

Finally everything is ready and the only thing we need to do now is to send the payload to the device and enjoy our lovely notification.

But wait, what happened to our friend Jimmy? Well, he was so inspired with the possibilities of iOS that he decided to quit his current job as a Ruby developer and become an iOS developer. Who knows, maybe he becomes CTO of a mobile app development company one day. :D

If you want to check the whole project, the source code is available here.

Thanks to Sinisa Cvahte for the design and Jung_Hanna for shredder animation.

Thank you for reading. Please comment, like or share it with your friends and we hope to see you soon.

Would you like to join us? Check out the open positions at our Careers page.

Undabot and Trikoder are partner organisations. We analyze, strategize, design, code and develop native mobile apps and complex web systems.

--

--