iOS SDK
Integrating Notify Partners push notifications into an iOS application — installation via Swift Package Manager, APNs setup, SDK initialization, and full API reference.
Requirements
- iOS 13.0+
- Swift 5.9+ (Swift 6 concurrency supported)
- Xcode 15+
- Apple Developer account with APNs configured
app_idfrom the Notify Partners dashboard
1. Installation via Swift Package Manager
- In Xcode: File → Add Package Dependencies...
- Enter the SDK repository URL
- Select a version (or the
mainbranch) - Add
NotifyPartnersSDKto your target
Alternatively, add the dependency in Package.swift:
dependencies: [
.package(url: "https://github.com/anthropics/notify-partners-ios-sdk", from: "1.0.0")
]The SDK has no external dependencies — only URLSession from Foundation is used.
2. APNs Setup
- In the Apple Developer Portal: enable Push Notifications for your App ID
- Create an APNs Key (recommended) or an APNs Certificate
- In Xcode: add the Push Notifications capability in your target settings
- Add the Background Modes capability → Remote notifications
3. SDK Initialization
Initialize the SDK in AppDelegate.didFinishLaunchingWithOptions:
Basic
import UIKit
import NotifyPartnersSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = NotifyPartnersConfig(appId: "YOUR_APP_ID")
NotifyPartnersSDK.shared.initialize(config: config)
return true
}
}With All Options
import UIKit
import NotifyPartnersSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = NotifyPartnersConfig(
appId: "YOUR_APP_ID",
externalId: "user-123",
requestTimeoutSeconds: 10,
resourceTimeoutSeconds: 30,
debugLogging: true,
manageNotificationDelegate: true
)
NotifyPartnersSDK.shared.setListener(self)
NotifyPartnersSDK.shared.initialize(config: config)
return true
}
}
extension AppDelegate: NotifyPartnersListener {
func onInitialized(instanceId: String) {
print("SDK ready: \(instanceId)")
}
func onInitializationFailed(error: SdkError) {
print("Init failed: \(error.localizedDescription)")
}
func onMessageReceived(msgId: String, title: String, body: String, data: [String: String]) {
print("Push received: \(title)")
}
}What Happens During Initialization
- A unique device token is generated (or restored from UserDefaults) and the device is registered on the server
- The notification delegate is configured (with forwarding to the previous delegate)
- Push notification permission is requested and the device registers for remote notifications
- Device info is sent to the server and periodic updates begin
- The listener is notified via
onInitialized(instanceId:)on success
4. Passing the APNs Token
Pass the APNs device token to the SDK in AppDelegate:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotifyPartnersSDK.shared.setAPNsToken(deviceToken)
}5. Lifecycle Events
Send lifecycle events from SceneDelegate:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidBecomeActive(_ scene: UIScene) {
NotifyPartnersSDK.shared.notifyLifecycle(.onscreen)
}
func sceneWillResignActive(_ scene: UIScene) {
NotifyPartnersSDK.shared.notifyLifecycle(.background)
}
}If the application does not use Scenes, use AppDelegate methods:
func applicationDidBecomeActive(_ application: UIApplication) {
NotifyPartnersSDK.shared.notifyLifecycle(.onscreen)
}
func applicationWillResignActive(_ application: UIApplication) {
NotifyPartnersSDK.shared.notifyLifecycle(.background)
}6. Push Notification Handling
The SDK automatically sets PushNotificationHandler.shared as the UNUserNotificationCenter delegate (when manageNotificationDelegate is true, which is the default). If your application already has a delegate, the SDK retains it and forwards all calls.
PushNotificationHandler automatically:
- On foreground delivery: sends a
deliveredevent, callsonMessageReceivedon the listener, and displays the notification (banner + sound + badge) - On notification tap: sends a
clickedevent
Both handlers extract msg_id from the push payload using NotifyPartnersConstants.msgIdKey.
Push Payload Format
{
"aps": {
"alert": {
"title": "Title",
"body": "Notification body"
}
},
"msg_id": "message-uuid",
"url": "https://example.com/action"
}Deep Link URLs
Extract the URL from the push payload for navigation:
if let url = userInfo[NotifyPartnersConstants.urlKey] as? String {
// Handle deep link
}Managing the Delegate Manually
let config = NotifyPartnersConfig(
appId: "YOUR_APP_ID",
manageNotificationDelegate: false
)
NotifyPartnersSDK.shared.initialize(config: config)
// Assign your own delegate:
UNUserNotificationCenter.current().delegate = yourDelegateWhen managing the delegate manually, call notifyDelivered/notifyClicked from your delegate.
7. Tags and Segmentation
// Set tags
try NotifyPartnersSDK.shared.setTags([
"plan": "premium",
"city": "moscow",
"interests": "sports"
])
// Update external ID
try NotifyPartnersSDK.shared.setExternalId("user-456")Validation limits:
- Maximum 50 tags per call
- Tag key: maximum 128 characters, must not be blank
- Tag value: maximum 256 characters
externalId: maximum 256 characters- Violations throw
SdkError.configurationError
Debouncing: rapid consecutive calls to setTags or setExternalId are coalesced (500ms window) — only the last value is sent to the server.
8. Shutdown
NotifyPartnersSDK.shared.shutdown()After shutdown, initialize(config:) can be called again with new configuration.
Full Example
AppDelegate.swift
import UIKit
import NotifyPartnersSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = NotifyPartnersConfig(appId: "YOUR_APP_ID", externalId: "user-123")
NotifyPartnersSDK.shared.initialize(config: config)
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotifyPartnersSDK.shared.setAPNsToken(deviceToken)
}
}SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidBecomeActive(_ scene: UIScene) {
NotifyPartnersSDK.shared.notifyLifecycle(.onscreen)
}
func sceneWillResignActive(_ scene: UIScene) {
NotifyPartnersSDK.shared.notifyLifecycle(.background)
}
}API Reference
NotifyPartnersSDK
@MainActor singleton. The main entry point.
@MainActor
public final class NotifyPartnersSDK {
public static let shared = NotifyPartnersSDK()
public private(set) var isInitialized: Bool
}initialize(config:)
Initialize the SDK. Call in AppDelegate.didFinishLaunchingWithOptions.
public func initialize(config: NotifyPartnersConfig)| Parameter | Type | Description |
|---|---|---|
config | NotifyPartnersConfig | SDK configuration (see below) |
Protected against double initialization — subsequent calls are ignored. Call shutdown() first to re-initialize.
shutdown
Release all resources: cancels pending operations, stops periodic updates, removes the listener.
public func shutdown()After shutdown, initialize(config:) can be called again.
setListener
Set the listener for SDK lifecycle events.
public func setListener(_ listener: (any NotifyPartnersListener)?)Pass nil to remove the listener. Set before initialize(config:) to receive onInitialized.
setAPNsToken
Pass the APNs device token. Call in AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken.
public func setAPNsToken(_ deviceToken: Data)| Parameter | Type | Description |
|---|---|---|
deviceToken | Data | Raw APNs token from the system |
setExternalId
Update the external user identifier.
public func setExternalId(_ externalId: String) throws(SdkError)| Parameter | Type | Description |
|---|---|---|
externalId | String | External user identifier (max 256 characters) |
- Throws
SdkError.configurationErrorif exceeds 256 characters - If SDK is not initialized, the call is silently ignored
- Debounced — rapid calls are coalesced
setTags
Set tags for user segmentation.
public func setTags(_ tags: [String: String]) throws(SdkError)| Parameter | Type | Description |
|---|---|---|
tags | [String: String] | Tags for segmentation (max 50, key ≤128, value ≤256) |
- Throws
SdkError.configurationErroron validation failure - If SDK is not initialized, the call is silently ignored
- Tags are replaced in full (not merged)
- Debounced — rapid calls are coalesced
notifyDelivered
Report that a push notification was delivered. Called automatically by PushNotificationHandler.
public func notifyDelivered(msgId: String)notifyClicked
Report that a push notification was clicked. Called automatically by PushNotificationHandler.
public func notifyClicked(msgId: String)notifyLifecycle
Send a lifecycle event.
public func notifyLifecycle(_ event: LifecycleEvent)| Value | When to call |
|---|---|
.onscreen | sceneDidBecomeActive / applicationDidBecomeActive |
.background | sceneWillResignActive / applicationWillResignActive |
.closed | On application termination (optional) |
isInitialized
public private(set) var isInitialized: BoolRead-only property. Returns true if the SDK has been initialized.
NotifyPartnersConfig
public struct NotifyPartnersConfig: Sendable {
public let appId: String
public let externalId: String?
public let requestTimeoutSeconds: TimeInterval
public let resourceTimeoutSeconds: TimeInterval
public let debugLogging: Bool
public let manageNotificationDelegate: Bool
}| Field | Type | Default | Description |
|---|---|---|---|
appId | String | — | Application ID (required) |
externalId | String? | nil | External user identifier |
requestTimeoutSeconds | TimeInterval | 10 | HTTP request timeout (seconds) |
resourceTimeoutSeconds | TimeInterval | 30 | HTTP resource timeout (seconds) |
debugLogging | Bool | false | Enable verbose logging |
manageNotificationDelegate | Bool | true | Automatically manage UNUserNotificationCenter delegate |
NotifyPartnersListener
Protocol for SDK lifecycle callbacks. All methods have default empty implementations — implement only what you need.
public protocol NotifyPartnersListener: AnyObject {
func onInitialized(instanceId: String)
func onInitializationFailed(error: SdkError)
func onMessageReceived(msgId: String, title: String, body: String, data: [String: String])
}| Callback | When it fires |
|---|---|
onInitialized(instanceId) | SDK registered with server successfully |
onInitializationFailed(error) | All retry attempts failed |
onMessageReceived(msgId, title, body, data) | Push message received while app is in foreground |
SdkError
Typed error enum for structured handling.
public enum SdkError: Error, LocalizedError, Sendable {
case networkError(String)
case serverError(httpCode: Int, body: String)
case clientError(httpCode: Int, body: String)
case timeoutError(String)
case configurationError(String)
}| Case | Retryable | Description |
|---|---|---|
networkError | Yes | DNS, offline, TLS failure |
serverError | Yes | HTTP 5xx |
timeoutError | Yes | Request timed out |
clientError | No | HTTP 4xx |
configurationError | No | Invalid config (blank appId, validation failure) |
NotifyPartnersConstants
Public constants for push notification payload keys.
public enum NotifyPartnersConstants {
public static let msgIdKey = "msg_id"
public static let urlKey = "url"
}Use these when extracting data from UNNotification.request.content.userInfo:
if let msgId = userInfo[NotifyPartnersConstants.msgIdKey] as? String {
NotifyPartnersSDK.shared.notifyClicked(msgId: msgId)
}LifecycleEvent
public enum LifecycleEvent: String, Sendable {
case onscreen
case background
case closed
}PushNotificationHandler
Built-in push notification handler. Implements UNUserNotificationCenterDelegate.
public final class PushNotificationHandler: NSObject, UNUserNotificationCenterDelegate {
public static let shared = PushNotificationHandler()
}Automatically installed when manageNotificationDelegate is true. Forwards all calls to the previous delegate if one was set.