Skip to content
Pushix Pushix
Features Developers Guides Pricing Questions
Sign in Start free
Menu
Features Developers Guides Pricing Questions
Sign in Create a free account
  1. Home
  2. Guides
  3. iOS

iOS push notifications

Add push notifications to an iPhone app in Swift: APNs key, Firebase Cloud Messaging, the Pushix SDK, pictures and buttons, badges, taps and in-app messages.

Updated 31 October 2026 12 min read Mobile apps

Mobile apps

  • Flutter
  • Android
  • iOS
  • React Native

Websites

  • Web push
  • WordPress

Your backend

  • REST API
  • PHP, Node.js, Python

Switching

  • Switching to Pushix
  • All guides
On this page 20 sections
  1. Requirements
  2. 1. Firebase and the APNs key
  3. 2. Add the package
  4. 3. Initialize
  5. API overview
  6. Permission
  7. Tags and users
  8. Handling taps
  9. While the app is open
  10. Buttons
  11. Badges
  12. Pictures and buttons (Notification Service Extension)
  13. In-app messages
  14. Using your own notification delegate
  15. Data-only messages
  16. Consent
  17. Info.plist keys
  18. Example app
  19. Testing
  20. Troubleshooting

The Pushix Swift package registers iPhones and iPads with Pushix, shows notifications while the app is open, tells your app when one is tapped, and shows in-app messages.

This guide is for native Swift and Objective-C apps. Flutter apps use the Flutter SDK instead.

Requirements#

  • Xcode 15.2 or newer (Firebase 11 needs it). The SDK works with Firebase 10 and 11; on older Xcode, pin Firebase 10 in your project.
  • iOS 13.0 or newer
  • A paid Apple Developer account (push needs an APNs key) and a real iPhone for testing
  • A Firebase project with your iOS app in it, the same project whose service account you upload to Pushix. iPhone notifications travel from Pushix through Firebase to Apple.

1. Firebase and the APNs key#

Four one-time steps:

Add the app to Firebase. In the Firebase console, open your project → Project settings → Your apps → Add app → iOS, with your app's bundle id. Download GoogleService-Info.plist and drag it into your app target in Xcode (tick "Copy items if needed" and your app target).

APNs key in Firebase. In the Apple Developer site create a key with Apple Push Notifications service (APNs) and download the .p8 file. In Firebase → Project settings → Cloud Messaging → Apple app configuration, upload it with its Key ID and your Team ID. One key works for all your apps.

Service account in Pushix. If you haven't yet: Firebase → Project settings → Service accounts → Generate new private key, then in the dashboard App settings → Channels → Android and iOS → upload the JSON file. It must be from the same project as GoogleService-Info.plist, or every send fails with SENDER_ID_MISMATCH.

Capabilities. In Xcode select your app target → Signing & Capabilities → + Capability, and add Push Notifications and Background Modes with Remote notifications ticked.

2. Add the package#

Copy the sdks/ios folder next to your app as pushix-ios (or put it in its own private Git repository). If the app already uses another push SDK, remove its package or pod: two push SDKs in one app fight over the notification delegate.

Swift Package Manager — in Xcode: File → Add Package Dependencies → Add Local… and pick the pushix-ios folder (or paste your Git repository URL). Add the Pushix library to your app target. Xcode fetches Firebase automatically.

In a Package.swift of your own:

Swift
dependencies: [
    .package(url: "https://github.com/you/pushix-ios.git", from: "1.0.0"),
],
targets: [
    .target(name: "YourApp", dependencies: [.product(name: "Pushix", package: "pushix-ios")]),
]

CocoaPods — in your Podfile, then run pod install:

Ruby
platform :ios, '13.0'
use_frameworks! :linkage => :static

target 'YourApp' do
  pod 'Pushix', :path => '../pushix-ios'
end

# For pictures and buttons: the Notification Service Extension (see below)
target 'NotificationService' do
  pod 'Pushix/Extension', :path => '../pushix-ios'
end

With CocoaPods both targets use import Pushix; with Swift Package Manager the extension uses import PushixExtension.

3. Initialize#

Call initialize in application(_:didFinishLaunchingWithOptions:). That is where iOS needs the notification delegate to be set, so taps that launch the app are received.

UIKit:

Swift
import UIKit
import Pushix

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Pushix.initialize("YOUR_APP_ID", serverUrl: "https://pushix.in", launchOptions: launchOptions)

    Pushix.Notifications.addClickListener { event in
      let data = event.notification.additionalData ?? [:]
      // route to a screen, e.g. data["screen"] as? String == "orders"
    }
    return true
  }
}

SwiftUI — add an app delegate with @UIApplicationDelegateAdaptor:

Swift
import SwiftUI
import Pushix

final class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
    Pushix.initialize("YOUR_APP_ID", serverUrl: "https://pushix.in", launchOptions: launchOptions)
    return true
  }
}

@main
struct MyApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {
    WindowGroup { ContentView() }
  }
}

Pushix calls FirebaseApp.configure() for you if your app hasn't. If you use other Firebase features, call FirebaseApp.configure() yourself before Pushix.initialize (Firebase crashes if it is configured twice).

Then ask for permission at a sensible moment:

Swift
Pushix.Notifications.requestPermission({ accepted in
  print("Notifications allowed: \(accepted)")
}, fallbackToSettings: true)

When the user logs in to your app, link the device to your user id so your backend can target them:

Swift
Pushix.login(String(user.id))   // on login
Pushix.logout()                 // on logout (also clears tags)

That's it. Run the app on an iPhone, allow notifications, and the device appears in the dashboard under Subscribers, marked iOS.

The SDK can be called from any thread; it does its work on the main thread and calls your listeners on the main thread.

API overview#

CallWhat it does
Pushix.initialize(appId, serverUrl: ..., launchOptions: launchOptions)Starts the SDK, in didFinishLaunching. Pushix.initialize(appId, withLaunchOptions: launchOptions) does the same with the server URL from Pushix_server_url in Info.plist
Pushix.Debug.setLogLevel(.verbose)How much the SDK writes to the Xcode console (.none … .verbose)
Pushix.login(id) / Pushix.logout()Links the device to your user id / unlinks it (also clears tags)
Pushix.setConsentRequired(true) / Pushix.setConsentGiven(true)Registers only after the user agrees
Pushix.Notifications.requestPermission({ accepted in }, fallbackToSettings: true)Asks for notification permission
Pushix.Notifications.permission / canRequestPermission / permissionNativePermission state (permissionNative is Apple's UNAuthorizationStatus)
Pushix.Notifications.addClickListener / addForegroundLifecycleListener / addPermissionObserver / addSilentDataListener and their remove…Taps, notifications arriving while the app is open, permission changes, data-only messages
Pushix.Notifications.clearAll() / setBadgeCount(n) / clearBadge()Notification Center and the app icon badge
Pushix.User.addTag(key:value:) / addTags / removeTag / removeTags / getTags() / setLanguageTags and message language
Pushix.User.externalId / Pushix.User.playerIdThe id passed to login; this device's player id
Pushix.User.pushSubscription.id / .token / .optedIn / optIn() / optOut() / addObserverPlayer id, FCM token and opt-in state of this device
Pushix.InAppMessages.addTrigger(_:withValue:) / addTriggers / removeTrigger / removeTriggers / clearTriggers / pausedIn-app message triggers, and holding messages back
Pushix.InAppMessages.addClickListener / addLifecycleListenerIn-app message events
class NotificationService: PushixNotificationServiceExtension {}Pictures and buttons (see below)

Listener protocols and their methods:

ProtocolMethod
PushixNotificationClickListeneronClick(event:)
PushixNotificationLifecycleListeneronWillDisplay(event:)
PushixNotificationPermissionObserveronNotificationPermissionDidChange(_:)
PushixPushSubscriptionObserveronPushSubscriptionDidChange(state:)
PushixSilentDataListeneronSilentData(notification:)
PushixInAppMessageClickListeneronClick(event:)
PushixInAppMessageLifecycleListeneronWillDisplay(event:), onDidDisplay(event:), onWillDismiss(event:), onDidDismiss(event:), all optional

Every listener also has a closure form, e.g. Pushix.Notifications.addClickListener { event in … }, which returns the listener object for remove….

Permission#

Swift
Pushix.Notifications.permission            // Bool: allowed (also true for provisional)
Pushix.Notifications.canRequestPermission  // true until the system dialog has been shown
Pushix.Notifications.addPermissionObserver { granted in /* update your UI */ }
  • The device registers right away, before permission, with notification_types 0. It becomes a subscriber when the user allows notifications.
  • iOS shows the system dialog only once. With fallbackToSettings: true, a later call shows an alert that opens the app's notification settings instead.
  • Permission is re-read whenever the app becomes active, so turning notifications on in Settings is noticed.

Tags and users#

Tags are key–value strings used for segments, filters and {{ personalisation }}.

Swift
Pushix.User.addTags(["level": 7, "plan": "gold", "last_bonus": Int(Date().timeIntervalSince1970)])
Pushix.User.addTag(key: "city", value: "Delhi")
Pushix.User.removeTag("plan")
let tags = Pushix.User.getTags()   // tags set from this device
Pushix.User.setLanguage("bn")      // pick the "bn" translation of messages

Numbers and booleans are sent as text. Your backend can also set tags by user id without the app: PUT /api/v1/apps/{app_id}/users/{external_user_id}. Changes made while offline are saved on the phone and sent the next time it can reach your server.

Swift
Pushix.User.pushSubscription.id        // player id, nil until registered
Pushix.User.pushSubscription.optedIn   // allowed and not opted out
Pushix.User.pushSubscription.optOut()  // stop push to this device without touching the permission
Pushix.User.pushSubscription.addObserver { state in print(state.current.id ?? "") }

Handling taps#

Swift
Pushix.Notifications.addClickListener { event in
  let n = event.notification
  print(n.notificationId)       // id from the REST API
  print(n.additionalData ?? [:]) // your "data" object
  print(n.launchURL ?? "")       // your "url"
  print(event.result.actionId ?? "body") // button id, or nil for the notification itself
  print(event.result.url ?? "")  // the button's url, or launchURL
}
  • Taps that open the app from closed are delivered as soon as you add the first click listener, so add it in didFinishLaunching.
  • http:// and https:// URLs (the message url, or a tapped button's url) open in Safari automatically. To handle them yourself, add Pushix_suppress_launch_urls = YES to Info.plist.
  • Deep links (myshop://orders) are not opened automatically; read event.result.url and navigate yourself.
  • Each tap is reported to the server and shows up as "Clicked" in the dashboard.

While the app is open#

Notifications are shown as a banner even when the app is in the foreground (with sound and badge when the message has them). To hide or delay one:

Swift
Pushix.Notifications.addForegroundLifecycleListener { event in
  if currentScreen == "orders" {
    event.preventDefault()             // don't show it
    // event.notification.display()    // or show it later
  }
}

A notification you preventDefault() stays hidden if you don't call display() within 25 seconds; calling display() after that shows it again as a new notification.

Buttons#

Send "buttons": [{"id": "claim", "text": "Claim", "url": "https://…"}] as for Android. With the Notification Service Extension added (see "Pictures and buttons" below), iPhones show them too: the extension registers a notification category for the buttons and puts it on the notification. Each button opens the app. The tapped button's id arrives as event.result.actionId and its url as event.result.url (http(s) links also open in Safari, see Handling taps). Without the extension iOS shows no buttons, only the notification.

ios_category is only needed for categories of your own, e.g. with actions that don't open the app or that ask for text. Register the category once in didFinishLaunching:

Swift
let claim = UNNotificationAction(identifier: "claim", title: "Claim", options: [.foreground])
let later = UNNotificationAction(identifier: "later", title: "Later", options: [])
UNUserNotificationCenter.current().setNotificationCategories([
  UNNotificationCategory(identifier: "OFFER_ACTIONS", actions: [claim, later], intentIdentifiers: [])
])

and send "ios_category": "OFFER_ACTIONS" (composer → iOS options → Category) with the same button ids in buttons, so the SDK can find each button's URL. When ios_category is set, Pushix leaves the buttons to your category. Categories Pushix creates are named pushix_…; your own categories are never changed, and only the 20 most recently used Pushix ones are kept.

Badges#

Send ios_badgeType Increase or SetTo with ios_badgeCount. Pushix keeps each iPhone's count on the server.

  • The SDK clears the number on the app icon when a session starts (the app opens, or comes back after 30+ seconds); the server resets its count at the same moment. On iOS 15 and older, iOS also removes the app's notifications from Notification Center when the badge is cleared.
  • To keep the badge, add Pushix_disable_badge_clearing = YES to Info.plist. The SDK then sends the badge number with each session, so the server's count stays in step and the next Increase adds to it.
  • Set the number yourself with Pushix.Notifications.setBadgeCount(3) or clearBadge(). Both also tell the server, so Increase continues from that number.

Pictures and buttons (Notification Service Extension)#

iOS only shows a picture (big_picture or ios_attachments) when the app has a Notification Service Extension that downloads it, and only shows buttons from a registered category. The Pushix extension does both. Pushix marks messages with a picture, or with buttons and no ios_category, as mutable-content automatically, so the extension runs for them.

  1. In Xcode: File → New → Target → Notification Service Extension, name it NotificationService. Set its deployment target to the same iOS version as the app.
  2. Add the PushixExtension library to the new target (target → General → Frameworks and Libraries → +). With CocoaPods use pod 'Pushix/Extension' as shown above.
  3. Replace the contents of NotificationService.swift with:
Swift
import PushixExtension   // CocoaPods: import Pushix

class NotificationService: PushixNotificationServiceExtension {}

The extension downloads the picture (waiting up to 20 seconds) and attaches it; if the download fails or is too slow the notification is shown without it. At the same time it registers the buttons' category and waits (up to 3 seconds) until iOS has it, because a notification that names a category iOS doesn't know yet shows no buttons. It doesn't use Firebase. To change a notification before it is shown, override willDeliver(_:for:); to wait a different time for the picture, override imageDownloadTimeout.

In-app messages#

Create them in the dashboard under In-app messages, with iOS ticked under platforms: a pop-up, card, top or bottom banner, or full screen, with your colours, picture and up to three buttons. The SDK downloads the ones meant for this device each time a session starts and shows them by their trigger:

TriggerShows
When the app opensOn each new session (launch, or back after 30+ seconds)
From the Nth sessionWhen the app opens and the device has had at least N sessions
After some time in the appN seconds into the session
When my code sets a triggerWhen your code calls addTrigger with a matching key and value
Swift
Pushix.InAppMessages.addTrigger("cart_value", withValue: "1200")
Pushix.InAppMessages.paused = true    // e.g. during a game round
Pushix.InAppMessages.paused = false

Pushix.InAppMessages.addClickListener { event in
  // event.result.actionId is the button id; event.result.url its link
  if event.result.action == "deep_link", event.result.url == "myshop://account" {
    router.showKyc()
  }
}

Messages appear in their own window above your app, inside the safe area, and keep the colours you chose in both light and dark mode. Banners and cards leave the rest of the screen usable. Frequency rules (show at most N times, wait between showings) are kept on the device. "Open a link" buttons open Safari; "Deep link" buttons are handed to your click listener (or opened with the system if you have none). Seen, clicked and closed counts appear in the dashboard and in Analytics.

For will/did display and dismiss events, implement PushixInAppMessageLifecycleListener (all methods optional) and pass it to Pushix.InAppMessages.addLifecycleListener(_:). To turn in-app messages off completely, add Pushix_disable_in_app_messages = YES to Info.plist.

Using your own notification delegate#

Pushix needs to see notification events to show notifications in the foreground and report taps. There are two ways to set that up.

Automatic (default). Pushix.initialize makes Pushix the UNUserNotificationCenter delegate. If your app set its own delegate before calling initialize, Pushix forwards everything that isn't a Pushix notification to it (foreground presentation, taps and "notification settings"), so your own local notifications keep working. Set your delegate first, then call initialize.

Manual. If your delegate must stay the delegate (or is set later, e.g. by another SDK), add Pushix_manual_notification_delegate = YES to Info.plist and forward the two calls:

Swift
extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              willPresent notification: UNNotification,
                              withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if Pushix.Notifications.handleWillPresent(notification, completionHandler: completionHandler) { return }
    completionHandler([.banner, .list, .sound])   // your own notifications
  }

  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -> Void) {
    if Pushix.Notifications.handleNotificationResponse(response, completionHandler: completionHandler) { return }
    // handle your own notifications here
    completionHandler()
  }
}

Both methods return true for Pushix notifications and then call completionHandler themselves. If something replaces the delegate after initialize without forwarding, Pushix notifications are not shown in the foreground or reported as clicked, and the SDK logs a warning.

APNs token. Firebase picks up the APNs token by itself through method swizzling. If you turned that off (FirebaseAppDelegateProxyEnabled = NO), pass the token on:

Swift
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  Pushix.didRegisterForRemoteNotifications(deviceToken: deviceToken)
}

Data-only messages#

Messages sent with content_available: true and no contents don't show anything. iOS hands them to your app delegate; pass them to Pushix and listen for them:

Swift
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  Pushix.Notifications.handleSilentNotification(userInfo)
  completionHandler(.newData)
}

Pushix.Notifications.addSilentDataListener { n in
  if n.additionalData?["refresh"] as? String == "balance" { reloadBalance() }
}

They need Background Modes → Remote notifications, and iOS limits how often they wake an app.

Consent#

To register only after the user agrees (GDPR):

Swift
Pushix.setConsentRequired(true)   // before initialize; remembered
Pushix.initialize(...)
// later, when the user agrees:
Pushix.setConsentGiven(true)

Firebase itself may create an FCM token at start-up; to stop that too, add FirebaseMessagingAutoInitEnabled = NO to Info.plist.

Info.plist keys#

KeyTypeEffect
Pushix_server_urlStringServer URL for Pushix.initialize(_:withLaunchOptions:)
Pushix_suppress_launch_urlsBooleanDon't open http(s) notification links in Safari; handle event.result.url yourself
Pushix_disable_badge_clearingBooleanKeep the app icon badge when a session starts
Pushix_manual_notification_delegateBooleanDon't become the notification delegate; forward calls yourself
Pushix_disable_in_app_messagesBooleanDon't download or show in-app messages

Example app#

sdks/ios/Example is a small SwiftUI app with the setup above, buttons for permission, login, tags, opt-out and in-app triggers, and a Notification Service Extension. Generate its Xcode project with XcodeGen (cd Example && xcodegen), add your GoogleService-Info.plist, set your team and bundle id, and run it on an iPhone.

Testing#

  1. Run the app on a real iPhone and allow notifications. The simulator only receives pushes on recent Xcode with an Apple silicon or T2 Mac.
  2. Turn on Pushix.Debug.setLogLevel(.verbose) and look for "Registered device …" in the Xcode console.
  3. In the dashboard, open Subscribers, click your device and tick Test device.
  4. New message → Send to test devices.

Troubleshooting#

ProblemFix
Device never appears in SubscribersCheck serverUrl (must be reachable from the phone over HTTPS) and the App ID. Turn on Pushix.Debug.setLogLevel(.verbose) and read the Xcode console.
"No APNs token yet" in the logAdd the Push Notifications capability, check the bundle id matches the iOS app in Firebase, and test on a real device.
"GoogleService-Info.plist is missing"Add the file from Firebase to the app target (Target Membership ticked).
Every send fails with SENDER_ID_MISMATCHThe service account in the dashboard is from a different Firebase project than GoogleService-Info.plist.
Registers but nothing arrivesUpload the APNs key in Firebase → Cloud Messaging. Firebase reports this as an authentication error in the message's error.
Nothing shows while the app is open, and taps aren't countedSomething replaced the notification delegate after Pushix.initialize. Set yours before, or forward to Pushix (see "Using your own notification delegate").
The tap that launched the app is missingCall Pushix.initialize in didFinishLaunching (not later), and add the click listener there too.
Crash "Default app has already been configured"Call FirebaseApp.configure() before Pushix.initialize, not after.
No pictureAdd the Notification Service Extension, and check its deployment target isn't higher than the phone's iOS version.
No buttonsAdd the Notification Service Extension (the SDK logs a warning when buttons arrive without it). With ios_category, register that category in the app (see Buttons). Long-press or swipe down the notification to see its buttons.
Badge disappears when the app opensThat's the default. Add Pushix_disable_badge_clearing to Info.plist.
In-app message never showsCheck its status is Live, iOS is ticked under platforms, and its frequency hasn't been used up on this device. Messages are fetched when a session starts.
Previous guideAndroid push notifications Next guideReact Native push notifications

Send your first notification

Create an app in Pushix to get your App ID and REST API key, then follow this guide. The free plan needs no card.

Create a free account See pricing

On this page

  1. Requirements
  2. 1. Firebase and the APNs key
  3. 2. Add the package
  4. 3. Initialize
  5. API overview
  6. Permission
  7. Tags and users
  8. Handling taps
  9. While the app is open
  10. Buttons
  11. Badges
  12. Pictures and buttons (Notification Service Extension)
  13. In-app messages
  14. Using your own notification delegate
  15. Data-only messages
  16. Consent
  17. Info.plist keys
  18. Example app
  19. Testing
  20. Troubleshooting
Back to top
Pushix Pushix

Push notification service for Android, iPhone and websites, from one dashboard or one API call. Built in West Bengal, India.

info@pushix.in

Product

Features Developers Pricing Questions Sign in Create an account

Guides

Flutter guide Android guide iOS guide React Native guide Web push guide WordPress guide REST API guide PHP, Node.js, Python guide Switching to Pushix guide All guides

Legal

Terms of Service Privacy Policy Cookie Policy Acceptable Use Policy Data Processing Addendum Refund & Cancellation Policy Service Delivery Policy

Company

Contact us All legal pages Support

© 2026 Pushix. All rights reserved.

Pushix · Powered by Pynix