iOS
Add Kustomer Chat to an iOS app with the KustomerChat Swift SDK. Swift Package Manager and CocoaPods setup, Kustomer.setup, presenting the chat UI, the headless ChatProvider and KUSChatListener, identity, theming, localization, and the privacy manifest.
The iOS SDK is a single Swift module, KustomerChat. It contains both the prebuilt chat and knowledge base UI and the headless API underneath it, so every integration starts with import KustomerChat.
Sample apps for SPM and CocoaPods: kustomer/ios-chat-sdk-samples.
Install
In Xcode, choose File › Add Package Dependencies… and enter:
https://github.com/kustomer/kustomer-ios-spmUse Up to Next Major Version from 8.3.0 and add the kustomer-ios product to your app target. Or in Package.swift:
dependencies: [
.package(url: "https://github.com/kustomer/kustomer-ios-spm", from: "8.3.0"),
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "kustomer-ios", package: "kustomer-ios-spm"),
]),
]The package is a prebuilt binary with no package dependencies to resolve.
platform :ios, '15.0'
use_frameworks!
target 'MyApp' do
pod 'KustomerChat', '~> 8.3.0'
endpod installDownload KustomerChat.xcframework from kustomer/kustomer-ios, drag it into your project, and set it to Embed & Sign. It contains device and simulator slices.
Info.plist
Add usage descriptions so customers can send photos and videos:
Files are picked with the system document picker, which needs no permission.
Set up
Get a chat key
Create an API key in Settings › Security › API Keys with only the
org.trackingrole and no expiration. Setup fails with any other roles.Call setup at launch
AppDelegate.swiftimport UIKit import KustomerChat @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let options = KustomerOptions() options.brandId = "YOUR_BRAND_ID" options.logLevels = [.error, .warning] _ = Kustomer.setup(apiKey: kustomerChatKey, options: options, launchOptions: launchOptions) { result in if case .failure(let error) = result { print("Kustomer setup failed: \(error)") } } return true } }- Call it from
application(_:didFinishLaunchingWithOptions:). In a SwiftUI app, use@UIApplicationDelegateAdaptor. setupreturns the client, and the result isn't discardable, so assign it to_.- Call it once. A second call fails with
alreadyConfigured, and options can't be replaced after setup.
- Call it from
Open chat
SwiftKustomer.show()
Kustomer is a global, so every call is Kustomer.method(…). Kustomer.configure(…) still works but is deprecated: setup always calls its completion. Check progress with Kustomer.status, which goes from uninitialized to configured, started and connected.
Options
KustomerOptions is a class; set properties before passing it to setup.
Change some settings at runtime:
Kustomer.overrideBrandId("OTHER_BRAND_ID") { error in }
Kustomer.changeBusinessSchedule(scheduleId: "SCHEDULE_ID") { result in }
Kustomer.changeActiveAssistant(.withId("ASSISTANT_ID"))
Kustomer.setHistoryStatusFilter(.open)Present the UI
UI methods are safe to call from any thread; the SDK moves to the main thread itself.
Kustomer.show() // follows the brand's widget setting
Kustomer.show(preferredView: .onlyChat)
Kustomer.show(preferredView: .knowledgeBase, presentingVC: self)
Kustomer.startNewConversation(
initialMessage: KUSInitialMessage(body: "Where is my order?", direction: .user),
customDescribe: ["orderNumberStr": "4417"],
afterCreateConversationOrError: { result in
if case .success(let conversation) = result { print(conversation.id) }
}
)
Kustomer.openConversation(id: "6716a3a9c4e5f60012ab34c1") { result in }
Kustomer.openChatAssistant(id: "ASSISTANT_ID", startDialog: nil, completion: { result in })
Kustomer.showKbArticle(id: "ARTICLE_ID")
Kustomer.showKbCategory(id: "CATEGORY_ID")
Kustomer.close(completion: nil)
Kustomer.isVisible- A
.userinitial message is sent and creates the conversation. A.agentone is shown as a greeting until the customer replies. customDescribesets conversation custom attributes, using attribute names with type suffixes.- In apps that use
UIScene, passpresentingVC:so the SDK presents from the right window. openChatAssistantandclosehave parameters without defaults, sostartDialog:andcompletion:are required, as above.
Unread counts and availability
Kustomer.getUnreadCount { result in
if case .success(let count) = result { badge.text = count > 0 ? "\(count)" : nil }
}
if Kustomer.isChatAvailable() == false {
// chat is off, or the business schedule says it's closed
}isChatAvailable() returns nil before chat settings have loaded, false when chat is off or outside business hours, and true otherwise. getOpenConversationCount counts conversations that aren't ended and had a message in the last 30 days.
Headless: ChatProvider
For your own UI, set options.enableUI = false and use Kustomer.chatProvider:
let chat = Kustomer.chatProvider
chat.createConversation(firstCustomerMessage: "Where is my order?", title: "Order 4417") { result in
switch result {
case .success(let created):
chat.sendChatMessage(text: "It was due Monday.", conversationId: created.conversation.id) { _ in }
case .failure(let error):
print(error)
}
}With enableUI = false, start and stop the connection yourself with Kustomer.start() and Kustomer.stop(). Never call stop() when you use the SDK's UI.
The knowledge base is on Kustomer.kbProvider: getRootCategory(lang:knowledgeBaseID:completion:), getCategory(by:childrenLimit:lang:knowledgeBaseID:completion:), searchArticles(term:page:pageSize:lang:completion:) and getArticle(by:lang:knowledgeBaseID:completion:). Satisfaction surveys are handled by the UI only.
Listen for events
Conform to KUSChatListener. Every method has a default implementation, so implement only the ones you need, and keep the returned handle to remove the listener later.
final class ChatEvents: KUSChatListener {
func onChatMessageReceived(conversationId: String, chatMessage: KUSChatMessage) { }
func onUnreadCountChange(count: Int) { }
func onUserIsTyping(conversationId: String, user: KUSUser, timetoken: KUSPNTtoken?, ai: Bool) { }
func onUserStopsTyping(conversationId: String, user: KUSUser, timetoken: KUSPNTtoken?) { }
func onConversationEnded(conversationId: String, conversation: KUSConversation) { }
}
let events = ChatEvents()
let handle = Kustomer.chatProvider.addChatListener(events)
// later
Kustomer.chatProvider.removeChatListener(handle)Connection state is on ConnectionProvider.shared: status is connecting, connected, reconnecting, disconnected or disconnectedUnexpectedly, and a KUSConnectionListener gets onPubNubConnectionStatusChange(status:).
Identity
if !Kustomer.isLoggedIn(userEmail: nil, userId: user.id) {
Kustomer.logIn(jwt: tokenFromServer) { result in
switch result {
case .success(let identity): print("Chat customer", identity.customerId)
case .failure(let error): print(error)
}
}
}
Kustomer.chatProvider.describeCurrentCustomer(sharedPhones: ["+15555550123"], custom: ["loyaltyTierStr": "gold"])
// on sign-out
Kustomer.logOut { error in }- The identity is stored in the keychain, so check
isLoggedIninstead of logging in on every launch. It comparesuserIdwith the token'sexternalId(case-insensitive), oruserEmailwith itsemail. - A successful login clears the local conversation cache and rebuilds the UI. The completion runs on the main thread, and calling
Kustomer.show()inside it is safe. logOutclears local state and deregisters push. It reports an error only if push deregistration failed, for example offline; local state is still cleared.logOutThenLogIn(jwt:_:)switches users in one call.- In
describeCurrentCustomer, use theshared*parameters;email,phone,emailsandphonesare deprecated.
The JWT comes from your server. See Customer identity.
Push notifications
setup registers for remote notifications. Forward the device token from your AppDelegate:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Kustomer.didRegisterForRemoteNotifications(deviceToken: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
Kustomer.didFailToRegisterForRemoteNotifications(error: error)
}APNs keys, permission prompts and notification delegates are covered in Push notifications.
Theming
Colors are customized by name, in either of two ways:
- Asset catalog. Add a color set named
KustomerColor.<name>to your app's asset catalog, with Any and Dark appearances. - Code. Assign
KustomerUI.Color.<name>before callingsetup.
KustomerUI.Color.outgoingMessageBubble = UIColor(named: "Brand")!
KustomerUI.Color.chatHeaderBackground = .systemBackgroundThe built-in colors support dark mode. Text uses Dynamic Type; there's no font API. On iOS 26 and later the UI uses the system's Liquid Glass styling, unless your app sets UIDesignRequiresCompatibility in Info.plist.
Localization
The SDK follows the device language; set options.language to force one. To change any string, add a localized Kustomer.strings file (for example in en.lproj) with the keys you want to override:
"com.kustomer.week" = "week";Run Kustomer.printLocalizationKeys() once to print every key. Strings are looked up in your localized Kustomer.strings first, then the SDK's own translation. A Kustomer.strings that isn't localized loses to the SDK's translations.
Privacy
The SDK includes a privacy manifest (PrivacyInfo.xcprivacy). It declares required-reason APIs for disk space, file timestamps, user defaults and system boot time, declares no collected data types, and doesn't track. Xcode merges it into your app's privacy report.