# 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
            // Add custom logic here...
        }
        contentHandler(bestAttemptContent)
    }

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

**1.** We set the local `contentHandler` and `bestAttemptContent` properties with the received ones and safely unwrap `bestAttemptContent` to have it as non-optional.  
**2.** The `bestAttemptContent` object is where we manipulate payload fields like `title`, `subtitle`, `badge`, `sound`, among others.  
**3.** Custom fields can be accessed via `userInfo`.  
**4.** We call `contentHandler` with our modified `bestAttemptContent`.  
**5.** In `serviceExtensionTimeWillExpire()`, we attempt to deliver the best effort content before termination.  

Also, since iOS 13.3, it's possible to receive notifications without displaying them to the user. You can add the `com.apple.developer.usernotifications.filtering` entitlement key to the Notification Service Extension target entitlements file, with value `YES`. To discard notifications, call the completion handler with a new `UNNotificationContent` instance:  

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

## How To Test

In recent Xcode versions, you can either drop an `.apns` file with your notification payload into the simulator or use the console to send notifications to your device or simulator. However, the notification service extension does not work on simulators at all — it never gets called.  

To test on a real device, you can simulate push notifications using [PushNotifications tester](https://github.com/onmyway133/PushNotifications?ref=strv.ghost.io) or `curl` commands. You'll need the `.p12` certificate or `.p8` token, along with your app's bundle ID, device notification token, and payload.  

For `PushNotifications`, just fill out the app. For `curl`, use commands like:  

```bash
// Certificate-based push
% 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
% 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}
```

Your payload must include `"alert"` key (to display alert) and `"mutable-content": 1` at the same level inside `aps`:  

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

Ensure your app has permission for push notifications, requesting if needed:  

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

## Troubleshooting

A common issue when adding a new extension to an existing project is incorrect values in the extension *Build Settings.* Make sure `Wrapper Extension` is `.appex` and `Executable Extension` is empty. Wrong values can cause the project and extension target to run on the device but the extension code will never execute.  

Another problem might be the notification permission settings—make sure notifications like *Alerts* are enabled, otherwise the extension won't work.  

Finally, ensure the bundle identifiers of your extensions are based on your main app's bundle ID. For example, if your app is `com.example.myApp`, your extension should be `com.example.myApp.MyAppExtension`.

## Conclusion

Working with app extensions can be a bit confusing at first, but it’s very fun and useful. You don’t need all of them in your project, but always consider extensions that can enhance your app experience by adding new possibilities for your users.  

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

## Sources

- [App Extensions](https://developer.apple.com/app-extensions/?ref=strv.ghost.io)
- [Modifying Content in Newly Delivered Notifications](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications?ref=strv.ghost.io)
- [Asking Permission to Use Notifications](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications?ref=strv.ghost.io)
- [iOS Today Extension Created as App Rather Than `.appex`](https://stackoverflow.com/questions/27303525/ios-today-extension-created-as-app-rather-than-appex/41016133?ref=strv.ghost.io#41016133)
- [Entitlements for User Notifications Filtering](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering?ref=strv.ghost.io)