Push Notifications for iOS apps

Add Apptics framework to your Apple project

By now, you would have already integrated Apptics with your Apple project. If not, refer to the integration guide.

  • Open the .xcworkspace file and make sure you have enabled push notification and app group identifier in your application.
  • Go to your root project in Xcode and select the main app target. Navigate to the Signing & Capabilities tab.
  • If push notifications are not enabled, click + Capability and add push notifications.

  • Click + Capability again and add Background modes.
  • Also, enable the remote notifications.

Add notification service extension

The AppticsNotificationServiceExtension enables your iOS app to receive the rich notifications with images, buttons, and badges. It is also essential for Apptics' confirmed devliery analytics stats.

  • In Xcode, navigate to File > New > Target.
  • Select the Notification Service Extension and click Next.

  • Give a name to the NotificationServiceExtension and click Finish.

  • When prompted to activate the scheme after selecting Finish, click Cancel to avoid activating it.

  • In Xcode, select the NotificationServiceExtension target.
  • Go to general settings. Set the minimum deployment to match your main application target. This should be iOS 14.5 or higher.

Add app groups

App groups enable your app and the NotificationServiceExtension to share data when a notification is received, even if the app is not running. This is essential for implementing the badges and confirmed deliveries.

  • Select your main app target in Xcode.
  • Go to Signing & Capabilities and click on + Capability.
  • Choose App groups.

  • Click on + to add a new group.
  • Set the app groups container name to 'group.MAIN_BUNDLE_IDENTIFIER.apptics', where the MAIN_BUNDLE_IDENTIFIER should match the bundle identifier of your main application.

  • Click OK to save the app group for your main app target. Repeat the steps for the NotificationServiceExtension Target.
  • Select the NotificationServiceExtension Target > Signing & Capabilities > + Capability > App groups.

  • In app groups, click + button to add a new group.
  • Set the app group container to 'group.MAIN_BUNDLE_IDENTIFIER.apptics', making sure NOT to include 'NotificationServiceExtension' in the name.
  • Replace the MAIN_BUNDLE_IDENTIFIER with the bundle identifier of your main application.

Add notification content extension (Carousel UI)

  • Create the extension target: File > New > Target.
  • Choose Notification Content Extension.

  • Create the extension target:

Install Apptics pods (CocoaPods)

  • Install the Apptics iOS SDK with Cocoapod.
  • Specify pod 'AppticsMessaging and AppticsNotificationServiceExtension' in your podfile.
  • The podfile will look something similar to the one shown below.
Copiedsource 'https://github.com/CocoaPods/Specs.git'
  target 'MAIN TARGET' do
    pod 'Apptics-SDK'
    pod 'AppticsMessaging'    

    # Pre build script will register the app version, upload dSYM file to the server and add apptics specific information to the main info.plist which will be used by the SDK.
    script_phase :name => 'Apptics pre build', :script => 'sh "./Pods/Apptics-SDK/scripts/run" --upload-symbols-for-configurations="Release, Appstore" --app-group-identifier="group.MAIN_BUNDLE_IDENTIFIER.apptics"', :execution_position => :before_compile          
  end

target 'NOTIFICATION EXTENSION TARGET' do
    pod 'AppticsNotificationServiceExtension'    
end

target 'NOTIFICATION CONTENT TARGET' do
    pod 'AppticsNotificationContentExtension'    
end
  • Add the AppticsMessaging, AppticsNotificationServiceExtension, and AppticsNotificationContentExtension frameworks.
Note: Pass the app group identifier to the run script as mentioned in the podfile above --app-group-identifier="group.MAIN_BUNDLE_IDENTIFIER.apptics".
  • From your terminal, navigate to the project root folder and run the pod install --repo-update.

Import and initialize in main application

Copied#import <Apptics/Apptics.h>
#import <AppticsMessaging/APMessaging.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions {
    [Apptics initializeWithVerbose:true];
    [APMessaging startService];
    return YES;
}
Copiedimport Apptics
import AppticsMessaging
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Apptics.initialize(withVerbose: true)
    APMessaging.startService()
    return true
}

Integrate the notification service extension

  • In the Xcode project navigator, select the NotificationServiceExtension folder.
  • Locate and open the NotificationService.m (for Obj-C) or NotificationService.swift (for Swift) file.
  • Replace the whole content of the file with the following code.
Copied#import "NotificationService.h"
#import <AppticsNotificationServiceExtension/AppticsNotificationServiceExtension.h>
@interface NotificationService ()
@property (nonatomic, copy) void (^contentHandler)(UNNotificationContent * _Nonnull);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                      withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    APPushNotificationExtension *apext = [APPushNotificationExtension new];
    apext.appGroup = @"group.MAIN_BUNDLE_IDENTIFIER.apptics";
    if ([apext isNotificationFromApptics:request]) {
        [apext didReceiveNotificationExtensionWithContent:self.bestAttemptContent
                                         contentHandler:contentHandler];
    } else {
        contentHandler(self.bestAttemptContent);
    }
}
- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}
@end
Copiedimport UserNotifications
import AppticsNotificationServiceExtension
final class NotificationService: UNNotificationServiceExtension {
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNMutableNotificationContent?
    override func didReceive(_ request: UNNotificationRequest,
                             withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        guard let bestAttemptContent else { return }
        let apext = APPushNotificationExtension()
        apext.appGroup = "group.MAIN_BUNDLE_IDENTIFIER.apptics"
        if apext.isNotificationFromApptics(request) {
            apext.didReceiveNotificationExtension(withContent: bestAttemptContent,
                                                   contentHandler: contentHandler)
        } else {
            contentHandler(bestAttemptContent)
        }
    }
    override func serviceExtensionTimeWillExpire() {
        if let bestAttemptContent {
            contentHandler?(bestAttemptContent)
        }
    }
}

Integrate notification content extension (Carousel UI)

Copied#import "NotificationViewController.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
#import <AppticsNotificationContentExtension/AppticsNotificationContentExtension.h>
static NSString *const kAppticsCarouselCategory = @"CUSTOM_CATEGORY_APPTICS_CAROUSEL";
@interface NotificationViewController () <UNNotificationContentExtension>
@property IBOutlet UILabel *label;
@property (nonatomic, strong) APNotificationContentExtension *apext;
@property (nonatomic, strong) UNNotification *bestAttemptContent;
@end
@implementation NotificationViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.label.hidden = YES;
}
- (void)dealloc {
    if ([self.bestAttemptContent.request.content.categoryIdentifier isEqualToString:kAppticsCarouselCategory] && self.apext) {
        [self.apext stopAutoScrollIfNeeded];
        [self.apext stopMessageCountdownIfNeeded];
    }
}
- (void)didReceiveNotification:(UNNotification *)notification {
    self.bestAttemptContent = notification;
    if ([self.bestAttemptContent.request.content.categoryIdentifier isEqualToString:kAppticsCarouselCategory]) {
        if (!self.apext) {
            self.apext = [[APNotificationContentExtension alloc] initWithViewController:self];
            [self.apext setupUI];
        }
        [self.apext didReceiveNotificationExtensionWithContent:self.bestAttemptContent contentHandler:nil];
        return;
    }
}
- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if ([self.bestAttemptContent.request.content.categoryIdentifier isEqualToString:kAppticsCarouselCategory] && self.apext) {
        [self.apext viewDidLayoutSubviews];
    }
}
@end
Copiedimport UIKit
import UserNotifications
import UserNotificationsUI
import AppticsNotificationContentExtension
final class NotificationViewController: UIViewController, UNNotificationContentExtension {
    private let kAppticsCarouselCategory = "CUSTOM_CATEGORY_APPTICS_CAROUSEL"
    @IBOutlet weak var label: UILabel!
    private var apext: APNotificationContentExtension?
    private var bestAttemptContent: UNNotification?
    override func viewDidLoad() {
        super.viewDidLoad()
        label.isHidden = true
    }
    deinit {
        if bestAttemptContent?.request.content.categoryIdentifier == kAppticsCarouselCategory, let apext {
            apext.stopAutoScrollIfNeeded()
            apext.stopMessageCountdownIfNeeded()
        }
    }
    func didReceive(_ notification: UNNotification) {
        bestAttemptContent = notification
        guard notification.request.content.categoryIdentifier == kAppticsCarouselCategory else { return }
        if apext == nil {
            apext = APNotificationContentExtension(viewController: self)
            apext?.setupUI()
        }
        apext?.didReceiveNotificationExtension(withContent: notification, contentHandler: nil)
    }
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if bestAttemptContent?.request.content.categoryIdentifier == kAppticsCarouselCategory {
            apext?.viewDidLayoutSubviews()
        }
    }
}

Carousel category requirement (important)

  • Add the following key-value pairs to the NotificationContentExtension’s Info.plist.
UNNotificationExtensionCategoryStringCUSTOM_CATEGORY_APPTICS_CAROUSEL
UNNotificationExtensionDefaultContentHiddenBoolYES
UNNotificationExtensionUserInteractionEnabledBoolYES
UNNotificationExtensionInitialContentSizeRatioNumber1

Your carousel notifications must arrive with:

CopiedUNNotificationContent.categoryIdentifier == "CUSTOM_CATEGORY_APPTICS_CAROUSEL"


In practice, that means your push payload must include aps.category = CUSTOM_CATEGORY_APPTICS_CAROUSEL, so iOS sets the notification category and the content extension runs the carousel UI logic.

Handling notification payload in app

APNotificationHandler is a central helper to handle push tap actions such as:

  • Notification body tap (default action)
  • Action button taps (actionButton[].action)
  • clickAction resolution from addInfo (JSON string or dictionary)
  • Deep link/URL open
Copied#import <AppticsMessaging/AppticsMessaging.h>
Copiedimport AppticsMessaging Swift

Set notification delegate on launch

Copied[UNUserNotificationCenter currentNotificationCenter].delegate = self;

Handle push tap in AppDelegate

Copiedfunc isAppticsPayload(_ userInfo: [AnyHashable: Any]) -> Bool {
    var parsed: [String: Any]?

    if let addInfo = userInfo["addInfo"] as? [String: Any] {
        parsed = addInfo
    } else if let addInfoString = userInfo["addInfo"] as? String,
              let data = addInfoString.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
        parsed = json
    }

    return parsed?["appticsPnID"] != nil
}
 
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response          withCompletionHandler:(void (^)(void))completionHandler {   
if ([self isNotificationFromApptics:request]) {  
       [[APNotificationHandler shared] handleNotificationResponse:response                                              
       completionHandler:completionHandler]; 
}else{
completionHandler();
}
}
 
func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
) {
  if self.isNotificationFromApptics(request) {
    APNotificationHandler.shared().handleNotificationResponse(
        response,
        completionHandler: completionHandler
    )
  }else{
    completionHandler()
    
  }
    
}

Build and test checklist

  • Confirm main app can register for remote notifications.
  • Confirm both extensions have the correct app group.
Copiedgroup.<MAIN_BUNDLE_IDENTIFIER>.apptics
  • Send a test carousel push with aps.category = CUSTOM_CATEGORY_APPTICS_CAROUSEL.
Copiedaps.category = CUSTOM_CATEGORY_APPTICS_CAROUSEL

 

Verify:

  • Notification Service Extension runs (SDK modifies notification content / sets category-related behavior)
  • Notification Content Extension displays the carousel UI when category matches

Configure notification service

Upload p12 certificate

Refer to this link for detailed steps on how to upload your p12 certificate to Apptics