iOS Push Notifications in Swift (APNs, CallKit)

Swift, iOS 13+. Notifications via APNs, calls via PushKit + CallKit. No Firebase needed.

🍎 iOS

Swift, iOS 13+. Notifications via APNs, calls via PushKit + CallKit. No Firebase needed.

Download the SDK from the dashboard (Integration guide) after you create a free account.

  1. Add the SDK to your project

    Unzip the download into your project's root folder (this creates PushConsoleSDK/), then pick one option:

    • Swift Package Manager: Xcode β†’ File β†’ Add Package Dependencies… β†’ Add Local… β†’ choose the PushConsoleSDK/ios folder.
    • CocoaPods:
    Podfile
    # Podfile
    pod 'PushConsole', :path => './PushConsoleSDK/ios'

    Requires iOS 13+. The SDK doesn't need Firebase.

  2. Enable capabilities in Xcode

    Target β†’ Signing & Capabilities:

    • Push Notifications
    • Background Modes β†’ tick Remote notifications and Voice over IP (for calls)
    Info.plist
    <!-- Info.plist (Xcode adds this when you tick Background Modes) -->
    <key>UIBackgroundModes</key>
    <array>
      <string>remote-notification</string>
      <string>voip</string>
    </array>
    <key>NSMicrophoneUsageDescription</key>
    <string>Microphone is used for calls</string>
  3. Initialize in AppDelegate

    Call initialize in didFinishLaunchingWithOptions: when a call arrives while the app is closed, iOS launches it in the background and the SDK must be ready immediately. SwiftUI apps: use @UIApplicationDelegateAdaptor(AppDelegate.self).

    AppDelegate.swift
    import UIKit
    import PushConsole
    
    @main
    class AppDelegate: UIResponder, UIApplicationDelegate, PushConsoleDelegate {
    
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            PushConsole.shared.delegate = self
            PushConsole.shared.initialize(
                appId: "YOUR_APP_ID",
                serverURL: URL(string: "https://tingpush.com")!,
                callConfiguration: PushConsoleCallConfiguration(localizedName: "MyApp") // omit if you don't use calls
            )
            return true
        }
    
        func application(_ application: UIApplication,
                         didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            PushConsole.shared.didRegisterForRemoteNotifications(deviceToken: deviceToken)
        }
    
        func application(_ application: UIApplication,
                         didFailToRegisterForRemoteNotificationsWithError error: Error) {
            PushConsole.shared.didFailToRegisterForRemoteNotifications(error: error)
        }
    
        func application(_ application: UIApplication,
                         didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                         fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            PushConsole.shared.handleRemoteNotification(userInfo)
            completionHandler(.newData)
        }
    }

    The APNs environment is detected automatically: Xcode debug builds = sandbox, TestFlight/App Store = production.

  4. Assign the user at login

    Swift
    // After login — your backend targets this External ID
    PushConsole.shared.login(externalUserId: "user-123")
    
    // On logout
    PushConsole.shared.logout()
    
    // Tag the user for targeted sends
    PushConsole.shared.setTags(["plan": "vip"])
  5. Handle notification & call events

    Swift
    extension AppDelegate {
        // User tapped a notification
        func pushConsole(didOpenNotification userInfo: [AnyHashable: Any]) {
            // navigate using userInfo["screen"]...
        }
    
        // User tapped Answer → connect the real call (WebRTC/SIP/Agora…)
        func pushConsole(didAcceptCall call: PushConsoleCall) {
            // call.id, call.callerName, call.data["room"]
        }
    
        // CallKit activated audio: start playing/recording
        func pushConsoleDidActivateAudioSession() { }
    
        func pushConsole(didDeclineCall call: PushConsoleCall) { }  // declined
        func pushConsole(didEndCall call: PushConsoleCall) { }      // hung up
        func pushConsole(didMissCall call: PushConsoleCall) { }     // missed
    }
    
    // The other side hung up → dismiss the call screen
    PushConsole.shared.endCall(callId: call.id)

βœ… After integrating, check

  1. Run the app on a real device with logging on: you should see [PushConsole] Device registered ….
  2. The project's Devices tab: the device appears; iOS devices show the VoIP badge if you use calls.
  3. The Send tab: send a test to that device.
  4. Choose πŸ“ž Call and test with the app open, closed and with the screen locked.

Ready to integrate?

Create an account to get your App ID and API Key and download the SDKs. Dashboard samples are pre-filled with your project details.