Adding the Mobile SDK to an iOS app
Last updated: 9 October 2026 · 10 min read
The iOS SDK uses system frameworks only and comes with a privacy manifest. Clomni sends iOS pushes straight to APNs with your .p8 key: iOS needs no Firebase.
Steps
-
1
Requirements
- iOS 15 or newer, Xcode 15 or newer.
- The App ID and the iOS API key (
ios_…) from the dashboard's Installation section. - For push: an Apple Developer account and an APNs key.
-
2
Add the package
Swift Package Manager: Xcode › File › Add Package Dependencies, enter
https://github.com/clomni/clomni-mobile-sdk.git, choose “Up to Next Major Version” from1.0.4and add theClomniMessengerproduct to the app target. InPackage.swift:dependencies: [ .package(url: "https://github.com/clomni/clomni-mobile-sdk.git", from: "1.0.4"), ], targets: [ .target(name: "App", dependencies: [ .product(name: "ClomniMessenger", package: "clomni-mobile-sdk"), ]), ]CocoaPods: the SDK is not on CocoaPods trunk, take the pod from the repository's tag. With
pod 'ClomniMessenger', '1.0.4'CocoaPods cannot find the package.# Podfile, in your app's target pod 'ClomniMessenger', :git => 'https://github.com/clomni/clomni-mobile-sdk.git', :tag => '1.0.4'
Users can send photos in the conversation. Add the permission texts to
Info.plist:<key>NSPhotoLibraryUsageDescription</key> <string>To send photos to support</string> <key>NSCameraUsageDescription</key> <string>To take a photo and send it to support</string> <key>NSMicrophoneUsageDescription</key> <string>To send voice messages to support</string>
Without
NSCameraUsageDescriptionthe Messenger does not offer the camera. WithoutNSMicrophoneUsageDescriptionthe microphone button does not show (voice messages come with SDK 1.0.2). -
3
Start the SDK
Call
initializeonce at launch. In a SwiftUI app, give it theAppDelegatewith@UIApplicationDelegateAdaptor; push needs it anyway.import SwiftUI @main struct ExampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var body: some Scene { WindowGroup { ContentView() } } }You can call the methods from any thread, callbacks arrive on the main thread. A second
initializeis ignored. The fullAppDelegateis in the push part below. -
4
User and Messenger
let user = ClomniUser(userId: "12345", email: "[email protected]", name: "Aysel") Clomni.loginUser(user, userHash: hashFromYourServer) // the hash comes from your server Clomni.updateUser(language: "az", customAttributes: ["plan": "premium"]) // Together with your app's own logout: Clomni.logout()
// SwiftUI Button("Support") { Clomni.present(source: "profile_support") } // UIKit @objc private func supportTapped() { Clomni.present(source: "profile_support") }The Messenger opens over the topmost screen. Call
presentafter the screen is shown, not indidFinishLaunching. Other calls:presentNewConversation(source:),presentConversation(_:),dismiss(). The unread count:let token = Clomni.addUnreadCountListener { count in ... }, laterClomni.removeUnreadCountListener(token). The floating button:Clomni.setLauncherVisible(true),Clomni.setBottomPadding(72)(points).Call
loginUseron every app start while the user is signed in, as if they had just signed in. For the same user the SDK reuses its stored session. -
5
Events, links, language and look
To listen to the Messenger's events:
Clomni.onMessengerOpened = { source in print("opened from \(source ?? "-")") } Clomni.onMessengerClosed = { print("closed") } Clomni.onConversationStarted = { id in print("conversation \(id)") } Clomni.onFlowCompleted = { flowId in print("flow \(flowId) completed") } Clomni.onUnreadCountChanged = { count in print("unread \(count)") }Each event has one listener;
nilremoves it.A news item's button can carry a web address or your app's deep link. With
onLinkset, every link comes to your app and the app opens it. Without it, the system opens links.Clomni.onLink = { url in if url.scheme == "example" { // route inside your app } else { UIApplication.shared.open(url) } }Language, sounds and your app's own colour:
Clomni.setLanguage("en") // az, en or ru; nil follows the phone Clomni.setSoundsEnabled(false) Clomni.setTheme(primaryColor: "#0A66C2", typeface: "Montserrat", mode: .dark)setLanguagepicks one of the languages turned on in the dashboard (Appearance › “Languages”). An empty value, or a language that is off, follows the phone's language, then the dashboard's main language.- Sounds: a short sound for a message sent or received while a conversation is open. It follows the phone's silent mode.
setThemeputs your app's own colour, font and mode over the dashboard's. The colour is#RRGGBB; the other brand colours are derived from it. Each call replaces the previous one, and a value left out stays the dashboard's. With a wrong format, the log sayssetTheme: primaryColor "…" is not #RRGGBB.- The font is the family name of a font in the app bundle, listed under
UIAppFontsinInfo.plist("Montserrat"forMontserrat-Regular.ttf). Text keeps following Dynamic Type. For the font alone:Clomni.setTypeface("Montserrat"). When the font is not found, the log saysfont family "…" is not in the app; the system font staysand the system font is used.
-
6
Push: capability
Xcode › Project navigator › project › TARGETS › app › Signing & Capabilities › + Capability › Push Notifications. The configuration should be “All”.
-
7
Push: AppDelegate
import ClomniMessenger import UIKit import UserNotifications final class AppDelegate: NSObject, UIApplicationDelegate, @preconcurrency UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Clomni.initialize(appId: "app_…", apiKey: "ios_…") UNUserNotificationCenter.current().delegate = self Task { let center = UNUserNotificationCenter.current() let granted = try? await center.requestAuthorization(options: [.alert, .sound, .badge]) if granted == true { UIApplication.shared.registerForRemoteNotifications() } } return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Clomni.setDeviceToken(deviceToken) } // A push while the app is open. Clomni pushes stay hidden while the Messenger is open. @MainActor func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification ) async -> UNNotificationPresentationOptions { Clomni.shouldShowForeground(notification.request.content.userInfo) ? [.banner, .list, .sound] : [] } // A tap: a Clomni push opens its conversation. @MainActor func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { if Clomni.handlePush(response.notification.request.content.userInfo) { return } // your app's own push } }- Keep both delegate methods
@MainActor, notnonisolated. Otherwise a tap on a notification stops the app with “Call must be made on main thread”. - Ask for the notification permission at a moment that suits your users. The sample asks at launch for brevity.
Clomni.isClomniPush(userInfo)tells whether a push is Clomni's.
- Keep both delegate methods
-
8
Sandbox and production
The SDK reports each device's APNs environment itself: a build run from Xcode uses sandbox; TestFlight, the App Store and Ad Hoc use production. One
.p8key serves both, provided it is created for Sandbox & Production. A key limited to one environment givesBadEnvironmentKeyInTokenin the other. The “Test push” list shows each device's environment.If you want the agent's photo in the notification, add a Notification Service Extension. Clomni sends every push with
mutable-content: 1and anavatar_url. The extension's ready code is in the iOS chapter of the full guide, and it does not need the Clomni SDK. -
9
Check it
- Run the app on a real iPhone (the simulator does not receive APNs pushes from Clomni), allow notifications and open the Messenger once.
- Dashboard › your channel › settings column › Push › “Test push” › choose the device › “Send”.
Logs are in the Xcode console and Console.app: subsystem
ai.clomni.messenger, categoryClomni. Common problems:- A log line starting with
api_keythat says the key is wrong or not for this platform (server errorinvalid_api_key): use theios_…key. It also happens when the dashboard's “iOS Bundle ID” differs from the app's. no window to present the messenger from yet: callpresentafter the screen is shown.- “Test push” says
BadEnvironmentKeyInToken: make a Sandbox & Production key.DeviceTokenNotForTopic: the Bundle ID in the Push section is not the app's.
-
10
Full guide
- PDF: Clomni Mobile SDK guide, in English, Azerbaijani and Russian.
- Online: iOS.
- In the dashboard: “Guide” in the channel's settings column.