# 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(rawValue: "MySoundName"))

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

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

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

### Explanation:
**1.** We set the local `contentHandler` and `bestAttemptContent` properties with the received ones and safely unwrap `bestAttemptContent`.

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

**3.** We can access custom fields via `userInfo`.

**4.** Call the `contentHandler` with our modified `bestAttemptContent`.

**5.** In `serviceExtensionTimeWillExpire()`, we unwrap `contentHandler` and `bestAttemptContent` to attempt delivering the modified content before the extension times out.

Additionally, since iOS 13.3, you can enable notifications without displaying them by adding the entitlement key `com.apple.developer.usernotifications.filtering` with value `YES` to the Notification Service Extension target entitlements file. You can then discard notifications by calling:

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

## How To Test

In the latest Xcode versions, you can either drop an `.apns` file containing your notification payload into the simulator or use the console to send notifications to your device or simulator. However, note that the notification service extension does **not** work on simulators; it never gets called. 

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

### Example `curl` commands:

**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}
```

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

### Request permission in the app:

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

## Troubleshooting

One common problem when adding a new extension to an existing project is incorrect values in the extension’s *Build Settings*. Ensure `Wrapper Extension` is `.appex` and `Executable Extension` is empty. Wrong values can allow the project to run but the extension code will never execute.

Another issue is if notification permissions for alerts are not enabled in system settings. If only `Sounds` and `Badges` are enabled, the extension won't work.

Finally, ensure your extension's bundle identifier is based on your main app's bundle ID. For example, if your app’s ID is `com.example.myApp`, your extension should be `com.example.myApp.MyAppExtension`.

## Conclusion

Working with app extensions might be confusing initially, but it’s really fun and useful. You don't need all extensions in your project, but finding the right one can significantly enhance your app experience.

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

## Sources

- [Apple Developer - App Extensions](https://developer.apple.com/app-extensions/?ref=strv.ghost.io)
- [Apple Documentation - Modifying Content in Notifications](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications?ref=strv.ghost.io)
- [Apple Documentation - Asking Permission for Notifications](https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications?ref=strv.ghost.io)
- [Stack Overflow - 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)
- [Apple Documentation - User Notifications Filtering Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_usernotifications_filtering?ref=strv.ghost.io)

---

Don't miss anything