Build a ride sharing iOS app with push notifications.

To follow this tutorial you will need a Mac with Xcode installed, knowledge of Xcode and Swift, basic knowledge of JavaScript (including Node.js), a Pusher account, and Cocoapods installed on your machine.

Ride sharing applications like to be able to use the Push Notifications feature. Also Push Notifications do not work on Simulators so you will need an actual iOS device to test.

Pusher’s Beams API has first-class support for native iOS applications. Your iOS app instances subscribe to Interests; then your servers send push notifications to those interests. Every app instance subscribed to that interest will receive the notification, even if the app is not open on the device at the time.

This section describes how you can set up an iOS app to receive transactional push notifications about your food delivery orders through Pusher.

Configure APNs

Pusher relies on Apple Push Notification service (APNs) to deliver push notifications to iOS application users on your behalf. When we deliver push notifications, we use your APNs Key. This page guides you through the process of getting an APNs Key and how to provide it to Pusher.

Head over to the Apple Developer dashboard by clicking here and then create a new Key as seen below:

When you have created the key, download it. Keep it safe as we will need it in the next section.

⚠️ You have to keep the generated key safe as you cannot get it back if you lose it.

Creating your Pusher application

The next thing you need to do is create a new Pusher Push Notification application from the Pusher dashboard.

When you have created the application, you should be presented with a Quickstart wizard that will help you set up the application.

In order to configure Push Notifications you will need to get an APNs key from Apple. This is the same key as the one we downloaded in the previous section. Once you’ve got the key, upload it to the Quickstart wizard.

Enter your Apple Team ID. You can get the Team ID from here. Click on the continue to proceed to the next step.

Updating your Rider application to support push notifications

In your client application, if you haven’t already, open the Podfile and add the following pod to the list of dependencies:

pod 'PushNotifications'

Now run the pod install command as you did earlier to pull in the notifications package. Next open the AppDelegate class and import the PushNotifications package:

import PushNotifications

Now, as part of the AppDelegate class, add the following:

let pushNotifications = PushNotifications.shared
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// [...]
      self.pushNotifications.start(instanceId: "PUSHER_NOTIF_INSTANCE_ID")
self.pushNotifications.registerForRemoteNotifications()
      // [...]
      return true
}
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// [...]
      self.pushNotifications.registerDeviceToken(deviceToken) {
try? self.pushNotifications.subscribe(interest: "rider_(AppConstants.USER_ID)")
}
      // [...]
}

💡 Replace PUSHER_PUSH_NOTIF_INSTANCE_ID with the key given to you by the Pusher application.

In the code above, we set up push notifications in the application(didFinishLaunchingWithOptions:) method and then we subscribe to the interest in the application(didRegisterForRemoteNotificationsWithDeviceToken:) method.

The dynamic interest demos how you can easily use specific interests for specific devices or users. As long as the server pushes to the correct interest, you can rest assured that devices subscribed to the interest will get the push notification.

Next, we need to enable push notifications for the application. In the project navigator, select your project, and click on the Capabilities tab. Enable Push Notifications by turning the switch ON.

Updating your Driver application to support Push notifications

Your rider application also needs to be able to receive Push Notifications. The process is similar to the set up above. The only difference will be the interest we will be subscribing to in AppDelegate which will be ride_requests.

Adding rich actions to our push notifications on iOS

Adding rich actions to our push notifications on iOS

As it currently stands, our application will be able to receive push notifications but let’s take it one step further and add rich actions to the application. This will add more engagement to the notification.

First, open the AppDelegate class and import the following classes:

import PushNotifications
import UserNotifications

Next, you need to extend the AppDelegate with the class. Then add the following code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// [...]
        let center = UNUserNotificationCenter.current()
center.delegate = self
        let cancelAction = UNNotificationAction(
identifier: "cancel",
title: "Reject",
options: [.foreground]
)
        let acceptAction = UNNotificationAction(
identifier: "accept",
title: "Accept Request",
options: [.foreground]
)
        let category = UNNotificationCategory(
identifier: "DriverActions",
actions: [acceptAction, cancelAction],
intentIdentifiers: []
)
        center.setNotificationCategories([category])
        // [...]
        return true
}

In the code above, we are specifying the actions we want our push notifications to display.

In the same AppDelegate class, add the following method which will handle the actions when they are selected on the push notification:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let name = Notification.Name("status")
        if response.actionIdentifier == "cancel" {
NotificationCenter.default.post(name: name, object: nil, userInfo: ["status": RideStatus.Neutral])
}
        if response.actionIdentifier == "accept" {
NotificationCenter.default.post(name: name, object: nil, userInfo: ["status": RideStatus.FoundRide])
}
        completionHandler()
}

In the code, we just send a local notification when the push notification action is tapped. Next, we will add an observer in our view controller that will trigger some code when the notification is received.

Open the MainViewController class and add the following code in the viewDidLoad method:

NotificationCenter.default.addObserver(
self,
selector: #selector(changeStatusFromPushNotification),
name: Notification.Name("status"),
object: nil
)

Next, add the changeStatusFromPushNotification method to the class:

@objc private func changeStatusFromPushNotification(notification: Notification) {
guard
let data = notification.userInfo as? [String: RideStatus],
let status = data["status"] else { return }
        sendStatusChange(status) { successful in
guard successful else { return }
            if status == .Neutral {
self.status = .FoundRide
self.cancelButtonPressed(UIButton())
}
            if status == .FoundRide {
self.status = .Searching
self.statusButtonPressed(UIButton())
}
}
}

This callback just triggers the sendStatusChange method that we have already defined earlier in the tutorial.

Creating our notification service extension

Next, we need to create our Notification Service Extension.

💡 When receiving a notification in an iOS app, you may want to be able to download content in response to it or edit the content before it is shown to the user. In iOS 10, Apple now allows apps to do this through a new Notification Service Extension. — Codetuts

In Xcode, go to File > New > Target… and select Notification Service Extension then give the target a name and click Done.

If you look in the file browser in Xcode, you should see the new target added with two new files: NotificationService.swift and info.plist. We will be modifying these files to make sure it gets and provides the right information for our push notification.

Open the NotificationService class and replace the didReceive method with the following:

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        func failEarly() {
contentHandler(request.content)
}
        guard
let content = (request.content.mutableCopy() as? UNMutableNotificationContent),
let apnsData = content.userInfo["data"] as? [String: Any],
let mapURL = apnsData["attachment-url"] as? String,
let attachmentURL = URL(string: mapURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!),
let imageData = try? NSData(contentsOf: attachmentURL, options: NSData.ReadingOptions()),
let attachment = UNNotificationAttachment.create(imageFileIdentifier: "image.png", data: imageData, options: nil)
else {
return failEarly()
}
        content.attachments = [attachment]
contentHandler(content.copy() as! UNNotificationContent)
}

In the code above, we try to get the content of the push notification. Since we want to display the map in the notification, we are expecting a static map URL from the custom data of the push notification. We use that and serve it as an attachment which we add the to content of the push. We finally pass the content to the contentHandler.

Next, add the following extension to the same file:

extension UNNotificationAttachment {
        static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
            do {
try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL!, options: [])
let imageAttachment = try UNNotificationAttachment(identifier: imageFileIdentifier, url: fileURL!, options: options)
return imageAttachment
} catch let error {
print("error (error)")
}
            return nil
}
}

The create method saves the static map to a temporary location on the device so it does not have to load from a URL.

One final change we want to make is in the info.plist file. Here we want to register all the action identifiers for the push notification. Open the info.plist file and add the following as highlighted in the image below;

That’s all we need to do on the application side. Now we need to make sure the API sends the push notifications.

Sending push notifications from our Node.js API

In the Node.js project, open our index.js file and import the push notification package:

const PushNotifications = require('pusher-push-notifications-node')
const pushNotifications = new PushNotifications({
instanceId: 'YOUR_INSTANCE_ID_HERE',
secretKey: 'YOUR_SECRET_KEY_HERE'
})

💡 You should replace the placeholder values with the values from your Pusher dashboard.

Next, add the following helper functions:

function sendRiderPushNotificationFor(status) {
switch (status) {
case "Neutral":
var alert = {
"title": "Driver Cancelled :(",
"body": "Sorry your driver had to cancel. Open app to request again.",
}
break;
case "FoundRide":
var alert = {
"title": "🚕 Found a ride",
"body": "The driver is on the way."
}
break;
case "Arrived":
var alert = {
"title": "🚕 Driver is waiting",
"body": "The driver outside, please meet him."
}
break;
case "OnTrip":
var alert = {
"title": "🚕 You are on your way",
"body": "The driver has started the trip. Enjoy your ride."
}
break;
case "EndedTrip":
var alert = {
"title": "🌟 Ride complete",
"body": "Your ride cost $15. Open app to rate the driver."
}
break;
}
if (alert != undefined) {
pushNotifications.publish(['rider'], {apns: {aps: {alert, sound: "default"}}})
.then(resp => console.log('Just published:', resp.publishId))
.catch(err => console.log('Error:', err))
}
}
    function sendDriverPushNotification() {
pushNotifications.publish(['ride_requests'], {
"apns": {
"aps": {
"alert": {
"title": "🚗 New Ride Request",
"body": `New pick up request from ${rider.name}.`,
},
"category": "DriverActions",
"mutable-content": 1,
"sound": 'default'
},
"data": {
"attachment-url": "https://maps.google.com/maps/api/staticmap?markers=color:red|37.388064,-122.088426&zoom=13&size=500x300&sensor=true"
}
}
})
.then(response => console.log('Just published:', response.publishId))
.catch(error => console.log('Error:', error));
}

Above we have two functions. The first is sendRiderPushNotificationFor which sends a notification to the rider based on the status of the trip. The second method is the sendDriverPushNotification which just sends a notification to the driver.

In the sendDriverPushNotification we can see the format for the push notification is a little different than the first. This is because we are supporting rich actions so we have to specify the category key and the mutable-content key. The category must match the name we specified in the AppDelegate.

Next, you need to call the functions above in their respective routes. The first function should be added to the POST /status route above the pusher.trigger method call. The second function should be called in the POST /request route above the pusher.trigger method call.

Now, when we run our applications, we should get push notifications on our devices.

⚠️ When working with push notifications on iOS, the server must be served in HTTPS.

That’s all there is to adding push notifications using Pusher. Heres a screen recording of our applications in action:

Conclusion

In this article, we created a basic ride sharing service and used that to demonstrate how to use Pusher to send push notifications with rich actions. Hopefully you learnt how you can use Pusher to simplify the process of sending Push Notifications to your users.

The source code to the repository is available on GitHub.

This Post was first Published on Pusher.


Build a ride sharing iOS app with push notifications. was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.