Add the Notification Service Framework to a native iOS project

The AcousticMobilePushNotification.xcframework enables support for media attachments (images, audio and video) as well as Acoustic dynamic action categories. Additionally, it automatically tracks push notification delivery by generating pushReceived events whenever a notification arrives on a device.

The Notification Service Extension functions as a second app within your overall app bundle and requires separate provisioning. When properly configured, the Extension allows the Acoustic SDK to detect incoming push notifications and send delivery analytics to Acoustic servers, regardless of whether the user interacts with the notification.

This article guides you through adding the Notification Service Extension to your iOS project, configuring it for either JSON-based or dictionary-based configuration and understanding how pushReceived events work across different app states.

🚧

Important

The notification service target must be compatible with the version of the Acoustic SDK you are using. So if you have a beta version of the SDK and want to switch to the stable version or visa versa, make sure you unlink the current target first and then add a new one.

Step A: Add a notification service target

  1. In your Xcode project, go to the File menu and select New > Target.

    Target menu item in Xcode
  2. Under iOS, select Notification Service Extension.

    Dialogue box
  3. In the Language list, select Objective-C. Fill out the remaining settings as appropriate.

  1. If a confirmation message appears, click Activate.
  1. Find a new target in your Xcode project.

Step B: Add the Acoustic notification framework to the target

  1. Select the Notification Service target and open the General tab.

  2. In the Signing section, select the same team the main project target belongs to and configure the bundle ID. A typical pattern for the bundle ID is com.company.app.notification if your app bundle ID is com.company.app.

  3. In the  Frameworks and Libraries section, add AcousticMobilePushNotification.xcframework.

  4. On the Build Settings tab, expand the Linking section. Add -ObjC to the Other Linker Flags build options for the Notification Service.

  5. On the Signing & Capabilities tab, add the App Group capability. Enable the same app group that the main application target uses.

  6. On the same tab, add the Keychain Sharing capability. Add the same value for the keychain group that the main application target uses.

To add media attachments to notifications, you must configure the mutable-content flag and media-attachment key in the iOS 10 payload. For more information, refer to the following articles:

Step C: Implementing the framework

Automatic implementation

In the NotificationService.h file, replace the contents with the following.

#import <UserNotifications/UserNotifications.h> #import <AcousticMobilePushNotification/AcousticMobilePushNotification.h> @interface NotificationService : MCENotificationService @end

In the NotificationService file, replace the contents with the following.

import UserNotifications import AcousticMobilePushNotification class NotificationService: MCENotificationService { }

Manual implementation

Objective-C projects with JSON-based configuration

If your project relies on JSON-based configuration, you must simply initialize the MCENotificationService without explicit config loading. The SDK will automatically read from the MceConfig.json file. This version doesn't need to import MCEManualConfiguration.h.

  1. In the NotificationService.h file, replace the contents with the following.
#if __has_feature(modules) @import UserNotifications; @import AcousticMobilePushNotification; #else #import <UserNotifications/UserNotifications.h> #import <AcousticMobilePushNotification/AcousticMobilePushNotification.h> #endif @interface NotificationService : UNNotificationServiceExtension @property MCENotificationService * notificationService; @end
  1. In the NotificationService.m file, replace the contents with the following.
#import "NotificationService.h" @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService -(instancetype)init { if(self = [super init]) { // JSON file-based configuration (uses MceConfig.json) self.notificationService = [[MCENotificationService alloc] init]; } return self; } - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { if(request.content.userInfo[@"notification-action"]) { [self.notificationService didReceiveNotificationRequest:request withContentHandler:contentHandler]; return; } } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. [self.notificationService serviceExtensionTimeWillExpire]; } @end

Objective-C projects with dictionary-based configuration

If your project relies on dictionary-based configuration, you must call MCEConfig sharedInstanceWithDictionary:MCEManualConfiguration.xmlSettings to initialize the SDK before creating the notification service. This approach requires importing the MCEManualConfiguration.h file.

  1. In the NotificationService.h file, replace the contents with the following.
#if __has_feature(modules) @import UserNotifications; @import AcousticMobilePushNotification; #else #import <UserNotifications/UserNotifications.h> #import <AcousticMobilePushNotification/AcousticMobilePushNotification.h> #endif @interface NotificationService : UNNotificationServiceExtension @property MCENotificationService * notificationService; @end
  1. In the NotificationService.m file, replace the contents with the following.
#import "NotificationService.h" #import "MCEManualConfiguration.h" @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService -(instancetype)init { if(self = [super init]) { // Dictionary-based configuration [MCEConfig sharedInstanceWithDictionary:MCEManualConfiguration.xmlSettings]; self.notificationService = [[MCENotificationService alloc] init]; } return self; } - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { if(request.content.userInfo[@"notification-action"]) { [self.notificationService didReceiveNotificationRequest:request withContentHandler:contentHandler]; return; } } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. [self.notificationService serviceExtensionTimeWillExpire]; } @end

Swift projects with JSON-based configuration

If you are using MceConfig.json for configuration, the Campaign library will automatically read from this file in your project bundle. Follow these steps to complete the implementation:

  1. Add MceConfig.json to the Notification Service target. You can verify the membership in File Inspector > Target Membership.
  2. Replace the contents of NotificationService.swift with the following:
import UserNotifications import AcousticMobilePush import AcousticMobilePushNotification class NotificationService: UNNotificationServiceExtension { let mobilePushNotificationService: MCENotificationService override init() { // JSON file-based configuration (uses MceConfig.json) mobilePushNotificationService = MCENotificationService() super.init() } override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { if request.content.userInfo["notification-action"] != nil { mobilePushNotificationService.didReceive(request, withContentHandler: contentHandler) return } // Handle other notifications here } override func serviceExtensionTimeWillExpire() { mobilePushNotificationService.serviceExtensionTimeWillExpire() } }

Swift projects with dictionary-based configuration

If your project relies on dictionary-based configuration, you must call MCEConfig.sharedInstance(with: Config.mobilePushConfig) to initialize the Acoustic SDK before creating the notification service. In that case, replace the contents of NotificationService.swift with the following:

import UserNotifications import AcousticMobilePush import AcousticMobilePushNotification class NotificationService: UNNotificationServiceExtension { let mobilePushNotificationService: MCENotificationService override init() { // Dictionary-based configuration MCEConfig.sharedInstance(with: Config.mobilePushConfig) mobilePushNotificationService = MCENotificationService() super.init() } override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { if request.content.userInfo["notification-action"] != nil { mobilePushNotificationService.didReceive(request, withContentHandler: contentHandler) return } // Handle other notifications here } override func serviceExtensionTimeWillExpire() { mobilePushNotificationService.serviceExtensionTimeWillExpire() } }

Understanding "push received" events

What is a "Push received" event?

When an iOS push notification arrives on a device, the Acoustic SDK automatically:

  1. Detects the push notification arrival.
  2. Generates a pushReceived event.
  3. Sets the event state to displayed (meaning it successfully arrived).
  4. Sends this event to the Acoustic server.

This happens regardless of whether the user interacts with the notification.

Detailed behavior by app state

If the Notification Service Extension to be properly configured, it behaves in the following way.

Foreground (app active)

  1. User is currently using the app.
  2. Push notification arrives.
  3. pushReceived event is sent immediately.
  4. Event is logged in Xcode console for debugging.

Background (app inactive)

  1. App is running but user switched to another app.
  2. Push notification arrives.
  3. pushReceived event is generated and sent to server.
  4. No Xcode console logging (this is normal behavior for background states).

Killed (app not running)

  1. App was force-quit or hasn't been launched.
  2. Push notification arrives on device.
  3. pushReceived event is still generated with state = displayed.
  4. Event is sent to Acoustic server.

User interaction flow

When a user taps on a notification action (like "Open URL" or "Open App"), iOS always brings the app to the foreground first and then executes the requested action.

Example: User taps a notification with "Open URL" action while app is in background:

  • App comes to foreground
  • Browser opens with the URL

Troubleshooting "Push received" events

If pushReceived events are not appearing in your analytics, check the following solutions.

For dictionary-based configurations:

  • Verify MCEConfig.sharedInstance(with:) is called in the init() method.
  • Confirm you're calling it with the correct configuration dictionary.
  • Check that the Notification Service Extension is properly configured.

For JSON-based configurations:

  • Confirm MceConfig.json is added to the Notification Service Extension target.
  • Verify target membership in Xcode File Inspector.
  • Check that the JSON file contains valid configuration.

For all configurations:

  • Review push notification states in the Mobile Metrics documentation.
  • Check device logs for any SDK errors.
  • Verify push notifications are arriving on the device.
  • Confirm your app has proper push notification permissions.