Push Notifications with AppsOnAir: Native iOS SDK Setup

Editorial team
Dot
November 2, 2026
Push notifications with AppsOnAir native iOS SDK setup – configure APNs, add the SDK, enable push and verify with a test notification

A complete setup guide for integrating AppsOnAir push notifications into native iOS apps (UIKit, SwiftUI, or Objective-C), covering APNs configuration, initialization, targeting, and production edge cases.

‍

Overview

The AppsOnAir iOS SDK delivers push notifications via APNs and surfaces a unified API for token management, segmentation, opt-in control, foreground display, and delivery analytics. Push targeting maps directly to the same distribution groups and app version segments already defined in the AppsOnAir console.

‍

Before starting: upload your APNs Auth Key (`.p8`) in the AppsOnAir console under App Settings → Push Notifications. The SDK will not deliver without credentials configured server-side.

A real device is required to receive APNs tokens. The SDK emits a mock token on Simulator so your registration flow remains testable.

‍

‍

Requirements

Requirement Minimum Version / Support
iOS 15.0 or later
Xcode 16 or later
Swift 6.2 (SPM) / 5.9 (CocoaPods)
Objective-C Fully supported through AOAPush* facade classes

‍

Basic push (Steps 1-5) covers: text notifications with title and body, tap-to-open and deep links, foreground display control, user targeting, tags, segments, and opt-in/out.

‍

Rich push (optional extension targets) covers: images in notifications, badge auto-increment, delivered analytics, and custom long-press view. Skip these if basic push is all you need.

‍

AppsOnAir Push Configuration 

Before starting SDK integration, I need a one-time push notifications configuration setup for the iOS platform. 

Credential configuration is a one-time setup following items needed. 

‍

AppsOnAir Push Configuration 

Credential configuration is a one-time setup following items needed. 

  • iOS App BundleID.
  • iOS TeamID and KeyID  from the apple developer account.
  • .P8 file for iOS from the apple developer account. (Make sure .p8 key should be Sandbox & production environment only)

For more details you visit documentation. 

‍


If you want to migrate your Existing OneSignal Push notification to AppsOnAir Push notification service you need export data from OneSignal Dashboard and received via email. Import all you existing data in AppsOnAir web portal under push notification section during first time setup as well as under devices section just tap on import devices CTA

To migrate your existing OneSignal push notification setup to AppsOnAir Push Notifications, export your existing data from the OneSignal Dashboard. You’ll receive the exported data via email.

Once you have the data, import it into the AppsOnAir web portal using either of these options:

  • During the initial setup: Import your existing data in the Push Notifications section.
  • Anytime later: Navigate to the Devices section and click the Import Devices CTA to import your existing device data.

‍

For more information visit OneSignal migration guide.

‍

‍

Step 1: Apple Capabilities

Two capabilities are required on the main app target. Without them the device will not register with APNs and no tokens will be generated.

In Xcode, select your app target → Signing & Capabilities:

  1. Add Push Notifications — required for APNs registration and push delivery.
  2. Add Background Modes → enable **Remote notifications — required for silent pushes and background delivery.

‍

If you plan to add the Notification Service Extension later, also add App Groups here (same group on both the main app and the NSE).

iOS always shows your app icon on notifications. APNs has no field for a custom notification icon. To change it, update the app icon in `Assets.xcassets` and ship a new build.

‍

‍

Step 2: Add the SDK

The SDK is distributed as three separate products so you only link what each target needs.

‍

Swift Package Manager: In Xcode: File → Add Package Dependencies, paste the repository URL, and resolve. Link products to targets:

‍

CocoaPods:

platform :ios, '15.0'
target 'MyApp' do
  use_frameworks!
  pod 'AppsOnAir-AppPush', '>= 1.0.6-beta', '< 1.1'
end
# Optional: only if adding extension targets
target 'NotificationService' do
  pod 'AppsOnAir-AppPush-ServiceExt', '>= 1.0.6-beta', '< 1.1'
end
target 'NotificationContent' do
  pod 'AppsOnAir-AppPush-ContentExt', '>= 1.0.6-beta', '< 1.1'
end

‍

Product Intended Target
AppsOnAir-AppPush Main application target
AppsOnAir-AppPush-ServiceExt Notification Service Extension target only
AppsOnAir-AppPush-ContentExt Notification Content Extension target only

‍

Then run:

pod repo update
pod install

‍

Open <project>.xcworkspace after install.

‍

Do not use the deprecated AppsOnAir-AppPush/ServiceExtension or /ContentExtension subspecs. With use_frameworks! They fail at archive time with Multiple commands producing AppsOnAir_AppPush.framework. Use the standalone pod names shown above.

‍

‍

Step 3: Configure Info.plist

Add AppsonairAppId to your `Info.plist` with the App ID from the AppsOnAir console under App Settings → Keys & IDs.

‍

If you use the Notification Service Extension, add AppsOnAirAppGroup to both the main app and NSE Info.plist files (covered in the NSE section below).

‍

‍

Step 4: Initialize the SDK

Call initialize() inside application(_:didFinishLaunchingWithOptions:) so the SDK registers with APNs before any scene connects. Call AppsOnAirBackgroundSync.registerHandlers() before initialize() — background sync handlers must be registered before any scene connects.

‍

Swift (UIKit):

/ AppDelegate.swift
import UIKit
import AppsOnAir_AppPush
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        AppPushService.Debug.logLevel = .verbose  // dev only
        // Must be called before any scene connects
        AppsOnAirBackgroundSync.registerHandlers()
        AppPushService.initialize(debug: true)
        AppPushService.setListener(self)
        AppPushService.Notifications.requestPermission()
        AppsOnAirBackgroundSync.scheduleIfNeeded()
        return true
    }
}
extension AppDelegate: PushListener {
    func onAPNsTokenUpdated(token: String, environment: APNsEnvironment) { }
    func onNotificationReceived(notification: PushNotification) { }
    func onNotificationOpened(notification: PushNotification) { }
    func onError(_ error: PushError) { }
}

‍

Swift (SwiftUI):

// MyApp.swift
import SwiftUI
import AppsOnAir_AppPush
@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
class AppDelegate: NSObject, UIApplicationDelegate, PushListener {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        AppPushService.Debug.logLevel = .verbose
        AppsOnAirBackgroundSync.registerHandlers()
        AppPushService.initialize(debug: true)
        AppPushService.setListener(self)
        AppPushService.Notifications.requestPermission()
        AppsOnAirBackgroundSync.scheduleIfNeeded()
        return true
    }
    func onAPNsTokenUpdated(token: String, environment: APNsEnvironment) { }
    func onNotificationReceived(notification: PushNotification) { }
    func onNotificationOpened(notification: PushNotification) { }
    func onError(_ error: PushError) { }
}

‍

Objective-C:

// AppDelegate.m
#import <UIKit/UIKit.h>
@import AppsOnAir_AppPush;
@interface AppDelegate : UIResponder <UIApplicationDelegate, AOAPushListener>
@property (strong, nonatomic) UIWindow *window;
@end
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [AOAPushDebug setLogLevel:AOALogLevelVerbose];
    [AOAPushBackgroundSync registerHandlers];
    [AOAPush initializeWithDebug:YES swizzle:YES];
    [AOAPush setListener:self];
    [AOAPushNotifications requestPermission];
    [AOAPushBackgroundSync scheduleIfNeeded];
    return YES;
}
// All PushListener methods are optional
- (void)onAPNsTokenUpdatedWithToken:(NSString *)token
                        environment:(AOAAPNsEnvironment)environment { }
- (void)onNotificationReceivedWithNotification:(AOAPushNotification *)notification { }
- (void)onNotificationOpenedWithNotification:(AOAPushNotification *)notification { }
- (void)onError:(NSError *)error { }

The SDK hooks APNs token and notification delegate methods automatically via swizzling. This works even when another library owns UNUserNotificationCenter.current().delegate — AppsOnAir pushes still reach the SDK and the other delegate keeps receiving its own notifications.

‍

‍

Step 5: Background Sync (Info.plist)

‍

Add the background task identifier to Info.plist so iOS grants permission to run background tasks:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.appsonair.push.background-sync</string>
</array>

‍

‍

Feature Guide

‍

Subscription Observer

‍

Observe token and opt-in state changes without polling. Register after initialize().

AppPushService.User.pushSubscription.addObserver(self)
func onPushSubscriptionDidChange(state: PushSubscriptionChangedState) {
    print("optedIn:", state.current.optedIn)
    print("token:", state.current.token ?? "")
}
// Observe login / logout
AppPushService.User.addObserver(self)
func onUserStateDidChange(state: UserChangedState) {
    print(state.current.externalId ?? "anonymous")
}

‍

Permission

let granted    = AppPushService.Notifications.permission
let canRequest = AppPushService.Notifications.canRequestPermission
if !granted && canRequest {
    AppPushService.Notifications.requestPermission()
}
// Permanently denied? Send to Settings:
AppPushService.Notifications.requestPermission(fallbackToSettings: true)
// Observe changes
AppPushService.Notifications.addPermissionObserver(self)
func onNotificationPermissionDidChange(permission: Bool) {
    print("permission granted:", permission)
}

‍

User Identity

AppPushService.login("user_12345")   // call when your user signs in
AppPushService.logout()              // call on sign-out; clears tags and aliases

‍

Tags and Segmentation

AppPushService.User.addTag(key: "plan", value: "premium")
AppPushService.User.addTags(["plan": "premium", "region": "us-east"])
AppPushService.User.removeTag("plan")
AppPushService.User.removeTags(["plan", "region"])
// Read from local cache (synchronous)
let cached = AppPushService.User.getTags()
// Fetch from backend (async)
AppPushService.User.getTags { tags in print(tags) }

‍

Language

AppPushService.User.setLanguage("fr")   // ISO 639-1
let lang = AppPushService.User.language

‍

Aliases and Email

AppPushService.User.addAlias(label: "crm_id", id: "CRM-9876")
AppPushService.User.addAliases(["crm_id": "CRM-9876", "hubspot_id": "HS-42"])
AppPushService.User.removeAlias("crm_id")
AppPushService.User.addEmail("user@example.com")
AppPushService.User.removeEmail("user@example.com")

‍

Opt-in and Opt-out

AppPushService.User.pushSubscription.optOut()   // stop delivery, preserve APNs token
AppPushService.User.pushSubscription.optIn()
let isOptedIn = AppPushService.User.pushSubscription.optedIn

‍

Foreground Display Control

By default the SDK shows banners when the app is in the foreground. Call `preventDefault()` to suppress and handle display yourself.

AppPushService.Notifications.addForegroundLifecycleListener(self)
func onWillDisplay(event: NotificationWillDisplayEvent) {
    event.preventDefault()  // suppress; omit this line to show normally
}

‍

Click and Open Listener

AppPushService.Notifications.addClickListener(self)
func onClick(event: NotificationClickEvent) {
    print(event.result.actionId ?? "body tap")
    print(event.result.url ?? "")
}

‍

Badge Count

AppPushService.setBadgeCount(5)
AppPushService.incrementBadgeCount(by: 1)
AppPushService.clearBadgeCount()
// Auto-clear badge when app comes to foreground (default = true)
AppPushService.autoClearBadgeOnForeground = false  // switch to count-down mode

‍

Silent Push

A silent push delivers data to your app without showing a notification. iOS wakes the app in the background for lightweight work such as a content sync. No notification permission is needed — silent pushes reach devices that declined it.

‍

Requirements:  Background Modes → Remote notifications enabled (Step 1), payload has aps.content-available: 1 and no alert, sound, or badge.

‍

Set the handler before initialize(). iOS gives you about 30 seconds.

AppPushService.onSilentPushReceived = { userInfo, completion in
    let syncType = userInfo["sync_type"] as? String
    // do lightweight background work
    completion(.newData)   // or .noData / .failed
}

‍

If your AppDelegate already implements didReceiveRemoteNotification:fetchCompletionHandler:, forward the push to the SDK from it:

func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    AppPushService.handleSilentPush(userInfo, fetchCompletionHandler: completionHandler)
}

‍

APNs silent payload:

{
  "aps": { "content-available": 1 },
  "notification_id": "sync-001",
  "sync_type": "orders"
}

‍

Remove Delivered Notifications

AppPushService.Notifications.clearAllNotifications()
AppPushService.Notifications.removeNotification(withIdentifier: "abc")
AppPushService.Notifications.removeNotifications(withIdentifiers: ["a", "b"])

‍

‍

REST API

In addition to the SDK, AppsOnAir supports triggering push notifications via REST API, useful for server-side sends, CI/CD workflows, and backend automation.

‍

Notification Service Extension (Optional — Rich Push)

‍

Basic push (Steps 1-5) works without the NSE. Add it only when you need:

Feature Requirements
Images in Notifications Notification Service Extension (NSE)
Automatic Badge Increment Notification Service Extension (NSE) + App Group
Delivery Analytics Notification Service Extension (NSE) + App Group
Custom Expanded Notification View (Long-Press) Notification Service Extension (NSE) + Notification Content Extension (NCE)

The NSE runs in its own process before iOS displays a notification. The AppsOnAir NSE downloads and attaches images, applies payload overrides, updates the badge, and records the 

Delivered analytics event.

‍

Step 1: Add the NSE Target

In Xcode: File → New → Target → Notification Service Extension. Name it NotificationService. When asked to activate the scheme, choose Cancel to keep debugging the main app. Set Minimum Deployments to the same version as your app (15.0+).

Xcode sets a new extension to the latest iOS by default. On devices running an older iOS, the extension silently never runs with no crash and no log.

‍

Step 2: Link the SDK to the NSE

SPM: In the package's Add to Target list, choose your NSE target for AppsOnAir-AppPush-ServiceExt. Link to the NSE only, never to the main app.

‍

CocoaPods: 

Module to import: `AppsOnAir_AppPush_ServiceExt`

target 'NotificationService' do
  pod 'AppsOnAir-AppPush-ServiceExt', '>= 1.0.6-beta', '< 1.1'
end

‍

Step 3: Share an App Group

The App Group lets the NSE read the App ID and subscription from the main app for delivery receipts, and keeps the running badge count.

‍

  1. Add App Groups capability to both the main app and NSE target, using the same identifier:
   group.<your-main-app-bundle-id>.appsonair

‍

  1. If you use that exact name, the SDK finds it automatically. Otherwise add it to both Info.plist files:
<key>AppsOnAirAppGroup</key>
<string>group.com.yourcompany.app.appsonair</string>

‍

  1. In the Apple Developer Portal, enable the App Group on both App IDs and regenerate provisioning profiles.

‍

Step 4: Replace NotificationService

‍

Swift (subclass, recommended):

// NotificationService/NotificationService.swift
import AppsOnAir_AppPush_ServiceExt
class NotificationService: AppsOnAirNotificationServiceExtension {
    // Nothing else required.
}

‍

Optionally modify content before the SDK applies its overrides:

class NotificationService: AppsOnAirNotificationServiceExtension {
    override func modifyContent(_ content: UNMutableNotificationContent,
                                request: UNNotificationRequest) {
        content.threadIdentifier = "orders"
    }
}

‍

Swift (existing NSE class or chaining another SDK):

import UserNotifications
import AppsOnAir_AppPush_ServiceExt
class NotificationService: UNNotificationServiceExtension {
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNMutableNotificationContent?
    private var receivedRequest: UNNotificationRequest?
    override func didReceive(_ request: UNNotificationRequest,
        withContentHandler handler: @escaping (UNNotificationContent) -> Void) {
        guard let content = request.content.mutableCopy() as? UNMutableNotificationContent
        else { return handler(request.content) }
        receivedRequest = request
        bestAttemptContent = content
        contentHandler = handler
        AppPushServiceExtension.didReceiveNotificationExtensionRequest(
            request, with: content, withContentHandler: handler)
        contentHandler = nil
    }
    override func serviceExtensionTimeWillExpire() {
        guard let request = receivedRequest, let handler = contentHandler else { return }
        handler(AppPushServiceExtension.serviceExtensionTimeWillExpireRequest(
            request, with: bestAttemptContent) ?? request.content)
        contentHandler = nil
    }
}

‍

Objective-C:

// NotificationService.m
#import "NotificationService.h"
@import AppsOnAir_AppPush_ServiceExt;
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
    self.receivedRequest    = request;
    self.contentHandler     = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    [AOAPushExtension didReceiveNotificationRequest:request
                                withContentHandler:contentHandler];
    self.contentHandler = nil;
}
- (void)serviceExtensionTimeWillExpire {
    [AOAPushExtension serviceExtensionTimeWillExpireRequest:self.receivedRequest
                                       bestAttemptContent:self.bestAttemptContent
                                           contentHandler:self.contentHandler];
    self.contentHandler = nil;
}
@end

‍

Step 5: Send mutable-content

iOS runs the NSE only when the payload contains "mutable-content": 1:

{
  "aps": {
    "alert": { "title": "New offer", "body": "Tap to view" },
    "mutable-content": 1
  },
  "notification_id": "notif-abc-123",
  "big_picture": "https://cdn.example.com/offer.jpg"
}

‍

Attachment rules:

Specification Details
Attachment Source Priority attachments (array, maximum 3) → big_picture → image_url → large_icon → video_url
Supported Protocol HTTPS only
Supported File Types JPG, JPEG, PNG, GIF, HEIC, WebP, MP4, M4V, MOV, MP3, M4A, WAV, AIFF
Download Timeout Maximum 15 seconds per download; 25 seconds total
Apple File Size Limits Images: 10 MB; Audio: 5 MB; Video: 50 MB
Failure Handling Failed attachments are discarded, but the notification is still displayed.

‍

‍

Notification Content Extension (Optional — Custom Long-Press View)

The NCE replaces the expanded (long-press) view of a notification. The SDK's built-in AppsOnAirContentViewController shows a full-width image, title, and body. It requires the NSE to download the image first.

‍

Step 1: Add the NCE Target

File → New → Target → Notification Content Extension. Name it NotificationContent. Set Minimum Deployments to match your app (15.0+).

‍

Step 2: Link the SDK to the NCE

SPM: Add AppsOnAir-AppPush-ContentExt to the NCE target only.

target 'NotificationContent' do
  pod 'AppsOnAir-AppPush-ContentExt', '>= 1.0.6-beta', '< 1.1'
end

Module to import: AppsOnAir_AppPush_ContentExt

‍

Step 3: Replace the View Controller

Delete MainInterface.storyboard (Move to Trash). The SDK builds its UI in code. Then replace NotificationViewController.swift:

// NotificationContent/NotificationViewController.swift
import UserNotifications
import AppsOnAir_AppPush_ContentExt
class NotificationViewController: AppsOnAirContentViewController {
    // Optional: customise on top of the built-in image + title + body
    override func configure(with notification: UNNotification) {
        super.configure(with: notification)
    }
}

‍

Step 4: Configure the NCE Info.plist

Replace NSExtensionMainStoryboard with NSExtensionPrincipalClass and set your category ID:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>UNNotificationExtensionCategory</key>
        <string>aoa_rich</string>
        <key>UNNotificationExtensionInitialContentSizeRatio</key>
        <real>1</real>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.usernotifications.content-extension</string>
    <key>NSExtensionPrincipalClass</key>
    <string>$(PRODUCT_MODULE_NAME).NotificationViewController</string>
</dict>

‍

Step 5: Send the Category

iOS opens the NCE only when aps.category matches UNNotificationExtensionCategory:

{
  "aps": {
    "alert": { "title": "Order shipped", "body": "Long-press to see details" },
    "category": "aoa_rich",
    "mutable-content": 1
  },
  "notification_id": "notif-abc-123",
  "big_picture": "https://cdn.example.com/parcel.jpg"
}

If a payload has actions but no aps.category, the NSE assigns a generated category for the buttons. That generated category won't match your NCE. Always send aps.category explicitly when you want the NCE.

‍

‍

Payload Reference

‍

Custom keys are siblings of aps, not inside it.

Key Type Description
notification_id String Available in callbacks as notification.id. Used to track notification delivery receipts.
title / subtitle / body String Overrides the corresponding values in aps.alert. Supported in the Notification Service Extension (NSE) only.
sound String Specifies "default" or the filename of a bundled .caf or .aiff sound file. Overrides aps.sound. Supported in the NSE only.
big_picture String (HTTPS URL) Specifies the primary notification image. Takes precedence over image_url and large_icon. Supported in the NSE only.
image_url String (HTTPS URL) Legacy alias for big_picture. Supported in the NSE only.
large_icon String (HTTPS URL) Specifies the notification image when big_picture and image_url are not provided. Supported in the NSE only.
video_url String (HTTPS URL) Specifies a video attachment when no image-related key is provided. Supported in the NSE only.
attachments Array Specifies up to 3 media attachments. Takes precedence over all individual image and video keys. Supported in the NSE only.
badge Integer Sets the app icon badge to an absolute count. Supported in the NSE only.
badge_increment Integer Increments the current badge count by the specified value. Requires the NSE and a shared App Group.
actions Array Defines notification action buttons using the format [{id, title, foreground?, destructive?}]. Actions must be registered by the NSE.
aps.category String Specifies the notification category used to route the notification to the Notification Content Extension (NCE), when configured.
aps.content-available Integer Set to 1 to request a background app wake-up. When sent without an alert payload, the push is silent and no visible notification is displayed.

‍

‍

When Notifications Won't Be Delivered

Push delivery depends on OS settings and device state, not just your code.

Condition Affects Effect
Low Power Mode Silent notifications only iOS may suspend background activity, delaying or preventing silent push processing. Visible notifications are unaffected.
Background App Refresh Disabled Silent notifications only iOS may not wake the app to process silent pushes.
App Force-Quit (swiped away from the App Switcher) Silent notifications only iOS generally does not relaunch the app for silent pushes until the user manually opens it. Visible notifications can still be delivered and tapped.
APNs Rate Limiting Silent notifications only Apple limits background push delivery. Excessive pushes may be delayed, throttled, or dropped.
Low Battery Silent notifications only iOS may defer background processing more aggressively as battery levels drop, even when Low Power Mode is disabled.
No Network Connection Silent and visible notifications APNs may retain notifications until the device reconnects, subject to the apns-expiration setting.
APNs on Simulator Silent and visible notifications The iOS Simulator does not support standard remote push notification registration in the same way as a physical device. Always test push notifications on a real device.
Notification Permission Denied Visible notifications only Visible notifications are not displayed when notification authorization is denied. Silent pushes do not require notification permission, but background delivery is not guaranteed.

‍

‍

Troubleshooting

Problem Solution
No APNs token on the device Ensure the Push Notifications capability is enabled under Signing & Capabilities for the app target.
Notification Service Extension (NSE) is not invoked Ensure the push payload includes "mutable-content": 1 in the aps dictionary.
NSE is not invoked on older iOS versions Ensure the NSE deployment target is compatible with the device's iOS version. Set the minimum deployment target to iOS 15.0.
Notification image is not displayed Verify that the URL uses HTTPS, the file type is supported, and the download completes within 15 seconds. Check the device console for [AppPushService NSE] logs.
Delivery analytics are missing Ensure the App Group is configured and that the same App Group identifier is used by both the main app and the NSE.
Badge increments are ignored Configure the App Group for shared badge state, or use the absolute badge value instead of badge_increment.
Notification Content Extension (NCE) does not appear on long-press Ensure aps.category matches the UNNotificationExtensionCategory value configured for the NCE. Both values must match exactly.
NCE displays a blank view or crashes Remove the NSExtensionMainStoryboard key and ensure NSExtensionPrincipalClass includes the module prefix: $(PRODUCT_MODULE_NAME).NotificationViewController.
NCE displays text but no image Verify that the NSE is configured correctly and that the notification includes a supported image attachment. The built-in NCE renders images only; video attachments are not rendered as images.
Archive fails with "Multiple commands produce framework" Replace deprecated CocoaPods subspec syntax with the standalone AppsOnAir-AppPush-ServiceExt and AppsOnAir-AppPush-ContentExt pods, as applicable.
pod install cannot find the specification Update the local CocoaPods specifications repository by running pod install --repo-update.
Silent push handler is not invoked Check that Background Modes → Remote notifications is enabled. Also verify that the app has not been force-quit and that Low Power Mode or other system restrictions are not affecting background execution.
Silent push handler is not invoked with a custom AppDelegate Ensure didReceiveRemoteNotification:fetchCompletionHandler: forwards the notification to the SDK by calling AppPushService.handleSilentPush(_:fetchCompletionHandler:).
Background synchronization does not run Call registerHandlers() before any scene connects to ensure background handlers are registered in time.
Objective-C error: objc_subclassing_restricted Swift classes cannot always be subclassed from Objective-C. For the NSE, use AOAPushExtension. For the NCE, subclass AOAContentViewController.
Warning: "Extension version must match parent app" Ensure the app, NSE, and NCE use the same CURRENT_PROJECT_VERSION build number.

‍

‍

Setup Checklist

‍

Main app

  • AppsonairAppId in Info.plist
  • Push Notifications and Background Modes → Remote notifications enabled
  • AppsOnAir-AppPush linked and initialize() called in didFinishLaunchingWithOptions
  • AppsOnAirBackgroundSync.registerHandlers() called before initialize()
  • BGTaskSchedulerPermittedIdentifiers contains com.appsonair.push.background-sync

‍

NSE (images, badge, delivered analytics)

  • NSE target added with Minimum Deployments matching the app
  • AppsOnAir-AppPush-ServiceExt linked to the NSE only
  • NotificationService subclasses AppsOnAirNotificationServiceExtension
  • Same App Group on both main app and NSE, enabled on both App IDs in Developer Portal
  • AppsOnAirAppGroup in both Info.plist files (unless using group.<bundle-id>.appsonair)
  • Pushes sent with mutable-content: 1

‍

NCE (custom expanded view)

  • NCE target added with Minimum Deployments matching the app
  • AppsOnAir-AppPush-ContentExt linked to the NCE only
  • MainInterface.storyboard deleted and NSExtensionPrincipalClass set
  • UNNotificationExtensionCategory matches the aps.category you send

‍

‍

Log Levels

Set before initialize():

Level Use
NONE Silent logging (default for production)
FATAL Fatal errors only
ERROR Errors
WARN Warnings
INFO General information
DEBUG Debug information
VERBOSE All messages (use during development)

‍

‍

Conclusion

The AppsOnAir Push Notification iOS SDK handles APNs registration, swizzling, and credential management behind a clean API. Basic setup is five steps: capabilities, SDK dependency, Info.plist, AppDelegate initialization, and background sync. Everything beyond that, segmentation, silent push, rich media, and custom long-press views, is opt-in via extension targets. For teams already using AppsOnAir for OTA distribution, push targeting plugs directly into existing build segments and distribution groups without additional configuration.

FAQ’s

No items found.

Actionable Insights,
Straight to Your Inbox

Subscribe to our newsletter to get useful tutorials , webinars,use cases, and step-by-step guides from industry experts

Start Pushing Real-Time App Updates Today
Try AppsOnAir for Free
Stay Uptodate