# App Extensions: Explore Notification Services Today

[**Breno Valadão**](https://www.strv.com/blog/authors/brenovaladao) Senior iOS Engineer

---

I’m happy to bring you a small introduction to *App Extensions*, focusing a bit on *Notification Service.*  
The goal is to provide you with a brief introduction. We are going to go step by step on how to add a new app extension, tips for simulating push notifications on your real device and advice in case you face similar issues we had.

## App Extensions

I believe this quote from [Apple’s official documentation](https://developer.apple.com/app-extensions/?ref=strv.ghost.io) summarizes the goal of App Extensions pretty well:

*"App extensions let you extend custom functionality and content beyond your app and make it available to users while they’re interacting with other apps or the system."*

Today, we have 36 different types of App Extensions templates, where 29 of them can be added to our iOS/iPadOS apps, allowing us to add custom extra functionality to areas like Notifications, Sharing, Siri Interactions and Messaging between others.

## The Notification Service Extension

The Notification Service Extension was designed to intercept any incoming push notification our applications receive, allowing us to modify its payload content — for example, changing the title, decrypting any encrypted data or even downloading media attachments.

## Adding the Notification Service Extension

Once you have your App project on Xcode, simply do *File -> New -> Target*, then select *Notification Service Extension*:

After choosing the template, you'll need to fill a couple of fields adding information to your extension such as *Product Name*, *Team*, *Language*, *Project* and *Embedded in Application*:

After adding it, you will have a new folder for your extension. Inside it, you will find a new file containing boilerplate code and an *info.plist* file. You should also have a new product inside the Products folder referring to the new app extension; its extension should be `.appex` (in case you have the product with a different extension other than `.appex`, check the *troubleshooting* section).

## Manipulating the Notification Payload

The manipulation of the data in the payload is quite simple, but it will mostly depend on your use case. In any case, the boilerplate code is a good starting point for a better understanding. You will see that our `NotificationsService.swift` is a class that conforms to `UNNotificationServiceExtension` protocol, which has two methods:

```swift
open func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void)

open func serviceExtensionTimeWillExpire()
```

The first method, as the official documentation says: *Call contentHandler with the modified notification content to deliver*. So, this is the place where we will work on preparing our new payload with our custom logic.

And the second method is called just before the extension will be terminated by the system. We may use this as an opportunity to deliver our "best attempt" at modified content; otherwise, the original push payload will be used.

The boilerplate code already has both methods added and the basic implementation should be something like the following:

```swift
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?

    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest,
                             withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {

        // 1
        self.contentHandler = contentHandler

        bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

        guard let bestAttemptContent = bestAttemptContent else {
            return
        }

        // 2
        bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
        bestAttemptContent.subtitle = "\(bestAttemptContent.subtitle) [modified]"
        bestAttemptContent.badge = 1
        bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName.MySoundName)

        // 3
        if var customDictionary = bestAttemptContent.userInfo["my_custom_key"] as? [AnyHashable: Any] {
            // 4
        }

        // Deliver the modified notification content
        contentHandler(bestAttemptContent)
    }

    override func serviceExtensionTimeWillExpire() {
        // 5
        if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}
```

### Explanation:

**1.** We set the local `contentHandler` and `bestAttemptContent` local properties with the received ones and, right after, we safely unwrap the `bestAttemptContent` in order to have it as non-optional.

**2.** The `bestAttemptContent` is the object where we can manipulate the payload fields, like `title`, `subtitle`, `badge`, `sound` among others.

**3.** We can also access custom fields using the `userInfo`.

**4.** Then we call the `contentHandler` with our new `bestAttemptContent` object.

**5.** In this case, we are using the boilerplate implementation for the `serviceExtensionTimeWillExpire()` method, which will unwrap the `contentHandler` and `bestAttemptContent` and will try to deliver the "best attempt" of the modified content, as mentioned before.

Also, it is worth noting that since iOS 13.3, we now have the capability to enable receiving notifications without displaying the notification to the user. With this, we'll be able to receive and analyze the notification and, based on our use case, we can simply discard the notification. This can be achieved by adding the `com.apple.developer.usernotifications.filtering` entitlement key to the *Notification Service Extension target entitlements file*, with value YES because it is a boolean type. Then, in case you don't want to display a notification to the user, you can call the completion handler passing a new `UNNotificationContent` instance after checking if you either want to display it or not:

```swift
// This will not deliver the notification to the user
contentHandler(UNNotificationContent())
```

## How To Test

Within the latest Xcode versions, we have the capability to either drop an `.apns` file containing our notification payload to the simulator or use the console to send notifications to our device or simulator. Then we could test the received push notifications quite easily. But, unfortunately, we can't test the notification service extension on simulators; it simply doesn’t work. The extension never gets called.

The good news is that we can quite easily test simulating push notifications in our real device using either [PushNotifications tester](https://github.com/onmyway133/PushNotifications?ref=strv.ghost.io) or *curl* commands. It's straightforward to use them; we only need to have either the `.p12` certificate or the `.p8` token for authentication, along with the *app bundle id*, *device notification token*, and the *push notification payload*.

For using the *PushNotifications tester*, just download it and fill out the information it needs. But, if you want to test using *curl*, you can do it by using the following commands:

```bash
// Certificate based push notification
% curl -v --header "apns-topic: ${TOPIC}" --header "apns-push-type: alert" --cert "${CERTIFICATE_FILE_NAME}" --cert-type DER --key "${CERTIFICATE_KEY_FILE_NAME}" --key-type PEM --data '<Push notification payload>' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}

// Token based push notification
% curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '<Push notification payload>' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
```

Remember that our push notification payload must satisfy two requirements for triggering the notifications service extension. First, we need to have the `alert` key, which will display the alert notification. Second, at the same level, we must include the `"mutable-content"` key with value `1`, both inside the `aps` key. See the example below:

```json
{
  "aps": {
    "alert": {
      "title": "Notification Title",
      "body": "Notifications body"
    },
    "mutable-content": 1
  }
}
```

Remember that you must be already registered for receiving push notifications in your app. If you haven't asked for permission yet, just call:

```swift
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
    // Handle permission granted or error
}
```

## Troubleshooting

One problem that could happen when adding a new extension to a project already running for a couple of years is that we can get wrong values in the extension *Build Settings*. In such situations, make sure you have the keys *Wrapper Extension* as `.appex` and *Executable Extension* as empty. If these keys have the wrong value, you'll still be able to run the project and the extension target on your device — but the extension code will never be executed.

Another issue could come up if we don’t have the *Alerts* notification permission enabled on system settings. If the user has only *Sounds* and *Badges* enabled, the extension won't work.

And last but not least, ensure that your extension's bundle identifiers are based on your main app target bundle identifier. For example, if your app bundle ID is `com.example.myApp`, your app extension must be `com.example.myApp.MyAppExtension`.

## Conclusion

Working with app extensions could be a bit confusing at the beginning, but it can absolutely be really fun and useful. You definitely won’t need to have all of them in your project, but I believe you can always find an extension that will improve your app experience a lot by bringing new possibilities for your users.

I hope you’ve enjoyed reading these tips and tricks, that's all from me.

## Sources

- [https://developer.apple.com/app-extensions/](https://developer.apple.com/app-extensions/)
- [https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications?ref=strv.ghost.io](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications)
- [https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications?ref=strv.ghost.io](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications)
- [https://stackoverflow.com/questions/27303525/ios-today-extension-created-as-app-rather-than-appex/41016133?ref=strv.ghost.io#41016133](https://stackoverflow.com/questions/27303525/ios-today-extension-created-as-app-rather-than-appex/41016133#41016133)
- [https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering?ref=strv.ghost.io](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering)