Docs04 / 06

iOS SDK

The Bildirim iOS SDK puts everything you need to add push notifications to your app in one package: device registration, the permission flow, click tracking, user tagging, and a ready-made Notification Service Extension for rich content (images, action buttons). It is written in Swift, has no dependencies (Foundation + UserNotifications) and runs on iOS 13 and later.

This page describes version 1.0.0 of the SDK — published on Swift Package Manager (git tag) and CocoaPods. An app that already has a Firebase/APNs integration can also be connected without the SDK, over REST; both routes use the same endpoints.

#Prerequisites

  1. An Apple Developer account and the Push Notifications capability for your app: Xcode → target → Signing & Capabilities+ CapabilityPush Notifications.
  2. An APNs key in the paneldeveloper.apple.comKeys → new key with Apple Push Notifications service (APNs) ticked. Enter the contents of the downloaded .p8 file, the Key ID, the Team ID and your app's Bundle ID under Settings → Mobile Push in the panel, then press Verify. The .p8 can be downloaded only once; keep it.
  3. A device or an Apple Silicon simulator. On an Apple Silicon Mac, an iOS 16+ simulator receives a real APNs token (we measured this) — you can start development without a device. Two caveats: xcrun simctl push injects the notification directly and bypasses the mutable-content pipeline, so it does not run the NSE (testing images and action buttons needs a real push); on Intel Macs and on simulators running iOS 15 or earlier, no token arrives at all.

#Installation

Swift Package Manager (Xcode → File → Add Package Dependencies):

https://github.com/bildirim-io/sdk-ios

Version rule: Up to Next Major 1.0.0. Add the Bildirim product to your app target.

CocoaPods:

pod 'Bildirim', '~> 1.0'

In AppDelegate (in SwiftUI apps, the class wired up with @UIApplicationDelegateAdaptor):

import Bildirim

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Bildirim.initialize(appKey: "pk_your_key")
    return true
}

The pk_ key is under Settings → API keys in the panel; the Settings → Mobile Push screen gives you this snippet with your key already filled in. It is a public key and embedding it in the app is fine.

The SDK intercepts the token callbacks (didRegisterForRemoteNotificationsWithDeviceToken, didReceive response) itself; you do not need to write anything else. If you would rather it did not (because you have your own UNUserNotificationCenterDelegate):

Bildirim.initialize(appKey: "pk_...", config: BildirimConfig(swizzle: false))

// and forward from two places yourself:
func application(_ app: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken token: Data) {
    Bildirim.setDeviceToken(token)
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completion: @escaping () -> Void) {
    if Bildirim.handleNotificationResponse(response) { completion(); return }
    // your own notification
}

For a self-hosted Bildirim, use BildirimConfig(apiBase: URL(string: "https://api.yourcompany.com")!).

#Ask for permission

Bildirim.requestPermission { granted in
    // granted=true: the device is registered
}

The system permission dialog appears once; if it is declined, the user can only re-enable it in Settings. So do not ask on first launch — ask after the user has seen some value. Our experience on the web is that a contextual request is accepted about twice as often. Explaining "why" on your own screen first (a pre-permission prompt) works especially well on iOS.

When permission is granted the SDK collects the APNs token and registers it with POST /v1/subscribe. The device's APNs environment is sent along with the registration (read from the aps-environment entitlement): a debug build installed from Xcode is sandbox, TestFlight and the App Store are production. The server sends to each subscriber from the server matching their own environment, so you never have to pick an environment in the panel.

Your device appears on the Subscribers screen with an iOS badge and its environment; send the first notification with Create notification → Send to my test device.

#Notification Service Extension — images, buttons, impression tracking

On iOS the system renders the notification; to attach a large image, show action buttons and measure a "shown" event, the notification has to pass through your app. That is what a Notification Service Extension (NSE) does. Things work without it (you get a text notification), but do not skip this step — images and buttons in your campaigns only appear on iOS with it.

  1. Xcode → File → New → TargetNotification Service Extension. Name it e.g. BildirimNSE. Choose Cancel when asked to activate the scheme.
  2. Add the SDK package to the NSE target as well, this time the BildirimNotificationServiceExtension product.
  3. Replace the contents of the NotificationService.swift Xcode generated with:
import BildirimNotificationServiceExtension

class NotificationService: BildirimNotificationService {}
  1. App Group (optional but recommended): add the App Groups capability to both targets, select the same group (group.<bundle-id>.bildirim) and pass BildirimConfig(appGroup: "group...."). The NSE uses it to share the impression event and badge information with the main app. Without a group, images and buttons still work; only the shown count is not measured on iOS.
  2. Give the NSE the same Deployment Target as your app; it needs its own provisioning profile (Xcode handles this under automatic signing).

What it does: for every Bildirim message carrying mutable-content: 1 it downloads and attaches the image, registers a category named bildirim_<campaign> for that notification if a (action buttons) is present so the buttons show, and reports the impression event to the server. The NSE runs with a 30-second limit and restricted memory; if the image cannot be downloaded the notification is shown as text rather than dropped.

When the user taps a notification, the SDK opens the address (url) by default: https:// in Safari, your own scheme (myapp://news/42) in your app (UIApplication.open). To route it yourself:

Bildirim.notificationOpenedHandler = { notification in
    // notification.url, notification.campaignId, notification.actionId (if a button was pressed)
    router.open(notification.url)
    return true // true = do not let the SDK do its default open
}

The click is reported to the server by the SDK; the numbers on the notification screen show web and mobile events together. On an action button click, actionId is the button's id; the notification screen also counts per button.

When the app is in the foreground the notification is shown as a banner by default. To decide yourself:

Bildirim.foregroundHandler = { notification in
    notification.campaignId != currentScreenCampaign // true → show it
}

#Badge

If badge is given in the campaign or the API, the number on the iOS app icon is set to that value (not incremented); 0 clears it. Clearing the badge when the app opens is your call: UIApplication.shared.applicationIconBadgeNumber = 0 (iOS 16+: UNUserNotificationCenter.current().setBadgeCount(0)).

#Identify, tag and measure users

The same names as in the web and Android SDKs:

Bildirim.login("user-42")                  // on sign-in — targetable from your server via externalIds
Bildirim.login("user-42", identityHash: signature)  // required if the project asks for identity verification
Bildirim.logout()                          // on sign-out — the device stays, the user link is cut
Bildirim.setTags(["city": "istanbul", "plan": "premium"])
Bildirim.setTags(["plan": nil])            // nil deletes the tag
Bildirim.track("purchase", properties: ["value": 149.9, "currency": "TRY"])
Bildirim.unsubscribe()                     // the user turned notifications off

If Settings → Keys → Identity verification is on in the panel, a signature is required on the login call: HMAC-SHA256(the project identity secret, externalId) in hex, generated on your server — never embed the secret in the app. An unsigned call gets 403 identity_verification_required; detail: Mobile integration → Identity verification.

Tags are merged; you do not need to send all of them on every call. A track event is attributed to a campaign clicked in the last 24 hours and appears as revenue. These calls are not lost when the network is down: the SDK keeps them in an ordered queue (UserDefaults) and flushes it when the connection returns. The token is stored in the Keychain; if the app is deleted and reinstalled a new token is issued, and thanks to the SDK's persistent install id the old record is replaced by the new one.

#Privacy manifest (PrivacyInfo.xcprivacy)

The SDK ships with its own PrivacyInfo.xcprivacy (required from iOS 17). What you need to declare in the App Privacy section of App Store Connect: Device ID (the push token and install id — for "App Functionality", not tracking) and Product Interaction (notification clicks and track events). No IDFA is collected, ATTrackingManager is never called, and no location is collected.

#Troubleshooting

Everything returns BadDeviceToken — a mixed-up environment. A token from an Xcode debug build belongs to Apple's sandbox server; the SDK reports the environment with the registration and the server picks the right one. If you see this error, an older version registering without the SDK (over REST) is probably not sending apnsEnvironment; the APNs environment setting in the panel is the default for those records.

TopicDisallowed / BadTopic — the Bundle ID entered in the panel does not match your app's. Pressing Verify in the panel catches this.

InvalidProviderToken — the .p8 is corrupt, or the Key ID/Team ID is wrong. Press Verify; it tells you why.

No image, no buttons — the NSE target is missing, does not inherit from BildirimNotificationService, or its Deployment Target is higher than the device's iOS version. Run the NSE scheme in Xcode and look at the os_log output.

No notifications on the simulator — there will not be any; an APNs token is only issued on a real device. (Xcode 14+ lets you drag an .apns file onto the simulator to test the payload; registration still needs a real device.)

The permission dialog does not appear — it was declined earlier; Settings → Notifications → your app. During development, deleting and reinstalling the app resets the dialog.

It works on TestFlight but not on the App Store (or the other way round) — both are the production environment; there is no difference. If you use a different Bundle ID (e.g. a .beta suffix), create a separate project in the panel for that Bundle ID.

More: Troubleshooting. Server-side detail and the REST route: Mobile integration.

Did not find what you were looking for? [email protected] and we will help — these pages grow with the questions we receive.Next: Using the API