cometchat-ios-core — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-ios-core (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ground truth:CometChatUIKitSwift ~> 5+CometChatSDK ~> 4(+CometChatCallsSDK ~> 5) — the installed Pods / SPM.swiftinterface+docs/ui-kit/ios. Official docs: https://www.cometchat.com/docs/ui-kit/ios/overview · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify Swift symbols/labels against the.swiftinterfacebefore relying on them.
This is the foundational skill for every CometChat iOS UI Kit v5 integration. It teaches HOW CometChat works on iOS — initialization, login, the manager pattern, and anti-patterns — so you can write project-appropriate code instead of relying on templates.
Read this skill first, before any placement or component skill.
A freshly-created Xcode project (File → New → App from the GUI) ships *no `Podfile`, no `Package.swift`, and no Swift Package Manager refs in `.xcodeproj/project.pbxproj**. Before touching any of the integration code below, you MUST establish a dependency-management mechanism — otherwise import CometChatUIKitSwift will hit Unable to resolve module dependency: 'CometChatSDK'` at the first build attempt and the entire integration is dead on arrival.
Detection:
ls Podfile Package.swift 2>/dev/null
grep -l "XCRemoteSwiftPackageReference\|repositoryURL.*cometchat" *.xcodeproj/project.pbxproj 2>/dev/nullIf all three return empty → fresh Xcode project, no dep manager. Pick one and set it up before continuing:
Option A — CocoaPods (most common, easiest to script):
cd <project-root>
cat > Podfile <<'POD'
platform :ios, '13.0'
use_frameworks!
target 'YourAppTargetName' do
pod 'CometChatUIKitSwift', '~> 5.1'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
end
end
end
POD
pod installAfter pod install, work from YourApp.xcworkspace (NOT .xcodeproj) — CocoaPods rewires the workspace to include the Pods project.
Option B — Swift Package Manager (no Podfile, no `.xcworkspace`):
The user must add the package via Xcode's GUI (the SPM dependency lives in *.xcodeproj/project.pbxproj and there's no clean CLI tooling to edit that file safely). Print these instructions verbatim:
1. Open<YourApp>.xcodeprojin Xcode 2. File → Add Package Dependencies… 3. Paste URL:https://github.com/cometchat/cometchat-uikit-ios4. Add Package → keep "Up to Next Major Version" defaults → Add Package again 5. ConfirmCometChatUIKitSwiftappears under your app target's Frameworks, Libraries, and Embedded Content
Then verify the package landed:
grep -E "cometchat-uikit-ios|CometChatUIKitSwift" *.xcodeproj/project.pbxproj | head -2If grep returns matches, the SPM dep is in. If it doesn't, the user didn't complete step 4 in Xcode — surface that explicitly and stop until they have.
HARD STOP if neither option is in place. Do not write import CometChatUIKitSwift into any Swift file until either pod install completes successfully or the SPM grep above returns matches. Skipping this step produces an integration that compiles only after the user does extra setup work — a worse outcome than asking them up-front.
Add to your Podfile:
platform :ios, '13.0'
use_frameworks!
target 'YourApp' do
pod 'CometChatUIKitSwift', '~> 5.1'
endThen run:
pod installImportant: Disable User Script Sandboxing (Xcode 15+)
After running pod install, you must disable user script sandboxing in your project's Build Settings:
.xcworkspace fileOr add this to your Podfile to do it automatically:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
end
end
endAdd the package URL in Xcode (File → Add Package Dependencies):
CometChat UI Kit (includes SDK):
https://github.com/cometchat/cometchat-uikit-iosCometChat SDK only (if needed separately):
https://github.com/cometchat/chat-sdk-iosCometChat Calls SDK (for voice/video calls):
https://github.com/cometchat/calls-sdk-iosOr add to Package.swift:
dependencies: [
.package(url: "https://github.com/cometchat/cometchat-uikit-ios", from: "5.0.0"),
// Optional: Add calls SDK for voice/video
// .package(url: "https://github.com/cometchat/calls-sdk-ios", from: "5.0.0") // requires iOS 16+
]| Package | Repository | Description |
|---|---|---|
| UI Kit | https://github.com/cometchat/cometchat-uikit-ios | Ready-to-use UI components |
| Chat SDK | https://github.com/cometchat/chat-sdk-ios | Core messaging SDK |
| Calls SDK | https://github.com/cometchat/calls-sdk-ios | Voice & video calling |
| Sample App | https://github.com/cometchat/cometchat-sample-app-ios | Sample implementation |
CometChat must be initialized exactly once before any UI component is used. Initialization is asynchronous and must complete fully before mounting any CometChat* view controller.
cometchat-settings.json (recommended)Version requirement (ENG-35866 — Skills Telemetry).CometChatUIKit.initFromSettings(completion:)reads a bundledcometchat-settings.jsonand lets the SDK self-reportintegrationSource = "ai-agent"to/user_sessions. It ships GA in `CometChatUIKitSwift >= 5.1.15` (public CocoaPods + SPM; pullsCometChatSDK 4.1.x). The~> 5.1Pod range /from: "5.0.0"SPM range below already resolve it. On an older UI Kit (< 5.1.15) the method does not exist — use the `UIKitSettings` builder fallback below.
Unlike the web/RN kits, the iOS initFromSettings takes no settings argument — it reads the file straight from the app bundle, so the file must be added to the target's Copy Bundle Resources.
Step 1 — create `cometchat-settings.json` and add it to the app target (File → Add Files…, and confirm it appears under Build Phases → Copy Bundle Resources). Fill appId / region / credentials.authKey; leave the rest at the defaults:
{
"appId": "APP_ID_HERE",
"region": "us",
"credentials": {
"authKey": "AUTH_KEY_HERE"
},
"chatSDK": {
"presenceSubscription": {
"type": "ALL_USERS",
"roles": []
},
"autoEstablishSocketConnection": true,
"adminHost": null,
"clientHost": null
},
"callsSDK": {
"host": null,
"adminHost": null,
"clientHost": null,
"callsHost": null
},
"uiKit": {
"subscribePresenceForAllUsers": true
}
}Step 2 — init (no builder, no args — the SDK reads the bundled file):
// initFromSettings ships GA in CometChatUIKitSwift >= 5.1.15 (ENG-35866)
import CometChatUIKitSwift
CometChatUIKit.initFromSettings { success, error in
if let error = error {
print("CometChat init failed: \(error.errorCode) — \(error.errorDescription)")
return
}
print("CometChat initialized")
// then: CometChatUIKit.login(uid:) — see §3
}cometchat-settings.json not found, the file isn't in Copy Bundle Resources — add it there (adding it to the project navigator alone is not enough).authKey ships in the app bundle either way; production uses server-minted auth tokens.import CometChatUIKitSwift
let uiKitSettings = UIKitSettings()
.set(appID: "YOUR_APP_ID")
.set(authKey: "YOUR_AUTH_KEY") // Required for dev mode
.set(region: "us") // "us", "eu", or "in"
.subscribePresenceForAllUsers() // Enable online/offline indicators
.build()Use a singleton manager to prevent double-init:
import CometChatUIKitSwift
import CometChatSDK
final class CometChatManager {
static let shared = CometChatManager()
private var isInitialized = false
private var initializationError: Error?
private init() {}
func initialize(
appID: String,
authKey: String,
region: String,
completion: @escaping (Result<Bool, Error>) -> Void
) {
guard !isInitialized else {
completion(.success(true))
return
}
let uiKitSettings = UIKitSettings()
.set(appID: appID)
.set(authKey: authKey)
.set(region: region)
.subscribePresenceForAllUsers()
.build()
CometChatUIKit(uiKitSettings: uiKitSettings) { result in
switch result {
case .success(let success):
self.isInitialized = success
completion(.success(success))
case .failure(let error):
self.initializationError = error
completion(.failure(error))
}
}
}
}Scene-based app (the modern Xcode default — has a `SceneDelegate.swift`)? Init inscene(_:willConnectTo:)instead — that's the canonical pattern the reference app uses (see "Init code — SceneDelegate (canonical)" below). ThedidFinishLaunchingWithOptionsexample here is correct for the SDK init call itself (it needs no window), but scene-based apps that load settings + apply theme + set the root view controller in one pass should do all of it inscene(_:willConnectTo:), where the window/scene exists. Use this AppDelegate form only for apps with no SceneDelegate.
import UIKit
import CometChatUIKitSwift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
CometChatManager.shared.initialize(
appID: "YOUR_APP_ID",
authKey: "YOUR_AUTH_KEY",
region: "us"
) { result in
switch result {
case .success:
print("CometChat initialized successfully")
case .failure(let error):
print("CometChat initialization failed: \(error)")
}
}
return true
}
}import SwiftUI
import CometChatUIKitSwift
@main
struct YourApp: App {
init() {
CometChatManager.shared.initialize(
appID: "YOUR_APP_ID",
authKey: "YOUR_AUTH_KEY",
region: "us"
) { result in
switch result {
case .success:
print("CometChat initialized successfully")
case .failure(let error):
print("CometChat initialization failed: \(error)")
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Use CometChatUIKit.login(uid:) with a test UID. Every new CometChat app comes with five pre-created test users: cometchat-uid-1 through cometchat-uid-5.
Important: The login callback uses .success and .onError cases, NOT Swift's standard Result type.
CometChatUIKit.login(uid: "cometchat-uid-1") { result in
switch result {
case .success(let user):
print("Logged in as: \(user.name ?? "")")
// Proceed to chat UI
case .onError(let error):
print("Login failed: \(error.errorDescription)")
@unknown default:
break
}
}Use CometChatUIKit.login(authToken:) with a token obtained from your backend:
CometChatUIKit.login(authToken: authToken) { result in
switch result {
case .success(let user):
print("Logged in as: \(user.name ?? "")")
case .onError(let error):
print("Login failed: \(error.errorDescription)")
@unknown default:
break
}
}// Synchronous — use when you know init is complete
if let currentUser = CometChatUIKit.getLoggedInUser() {
print("Logged in as: \(currentUser.name ?? "")")
}if let currentUser = CometChat.getLoggedInUser() {
CometChatUIKit.logout(user: currentUser) { result in
switch result {
case .success:
print("Logged out successfully")
case .onError(let error):
print("Logout failed: \(error.errorDescription)")
@unknown default:
break
}
}
}CometChat uses CometChatException for errors. Important: Use errorDescription property, NOT localizedDescription.
// CometChatException has these properties:
error.errorCode // String - error code like "ERR_UID_NOT_FOUND"
error.errorDescription // String - human-readable description
// NOTE: CometChatException exposes ONLY errorCode + errorDescription.
// There is no `details` property — don't reference one.CometChatUIKit.login(uid: "user-123") { result in
switch result {
case .success(let user):
print("Logged in: \(user.name ?? "")")
case .onError(let error):
print("Error: \(error.errorDescription)")
print("Code: \(error.errorCode)")
}
}// For onError closures where error might be optional:
CometChat.getUser(UID: "user-123") { user in
print("User: \(user?.name ?? "")")
} onError: { error in
// error is CometChatException? (optional)
print("Error: \(error?.errorDescription ?? "Unknown error")")
}
// For ApiStatus enum results:
CometChatUIKit.create(user: newUser) { result in
switch result {
case .success(let user):
print("Created: \(user.name ?? "")")
case .onError(let error):
// error is CometChatException (non-optional)
print("Error: \(error.errorDescription)")
}
}| Code | Description |
|---|---|
ERR_UID_NOT_FOUND | User doesn't exist |
ERR_ALREADY_LOGGED_IN | User already logged in |
ERR_NOT_LOGGED_IN | No active session |
AUTH_ERR_AUTH_TOKEN_NOT_FOUND | Invalid auth token |
ERR_INVALID_APP_ID | Wrong App ID |
ERR_INVALID_API_KEY | Wrong API/Auth Key |
// Constants.swift
struct CometChatConstants {
static let appID = "YOUR_APP_ID"
static let authKey = "YOUR_AUTH_KEY"
static let region = "us"
}Important: Add Constants.swift to .gitignore for production apps.
Add keys to your Info.plist:
<key>CometChatAppID</key>
<string>YOUR_APP_ID</string>
<key>CometChatAuthKey</key>
<string>YOUR_AUTH_KEY</string>
<key>CometChatRegion</key>
<string>us</string>Read them in code:
guard let appID = Bundle.main.object(forInfoDictionaryKey: "CometChatAppID") as? String,
let authKey = Bundle.main.object(forInfoDictionaryKey: "CometChatAuthKey") as? String,
let region = Bundle.main.object(forInfoDictionaryKey: "CometChatRegion") as? String else {
fatalError("CometChat credentials not found in Info.plist")
}Create Debug.xcconfig and Release.xcconfig:
// Debug.xcconfig
COMETCHAT_APP_ID = your_app_id
COMETCHAT_AUTH_KEY = your_auth_key
COMETCHAT_REGION = usReference in Info.plist:
<key>CometChatAppID</key>
<string>$(COMETCHAT_APP_ID)</string>The recommended pattern for iOS is a singleton manager that handles initialization, login state, and provides a clean API for the rest of the app.
Important: CometChatException does NOT conform to Swift's Error protocol. Use CometChatException directly in your callbacks, not Result<T, Error>.
import Foundation
import CometChatUIKitSwift
import CometChatSDK
final class CometChatManager {
// MARK: - Singleton
static let shared = CometChatManager()
// MARK: - State
private(set) var isInitialized = false
private(set) var currentUser: User?
// MARK: - Callbacks
var onLoginStateChanged: ((User?) -> Void)?
private init() {}
// MARK: - Initialization
func initialize(
appID: String,
authKey: String,
region: String,
completion: @escaping (Bool, CometChatException?) -> Void
) {
guard !isInitialized else {
completion(true, nil)
return
}
let uiKitSettings = UIKitSettings()
.set(appID: appID)
.set(authKey: authKey)
.set(region: region)
.subscribePresenceForAllUsers()
.build()
CometChatUIKit.init(uiKitSettings: uiKitSettings) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let success):
self?.isInitialized = success
self?.currentUser = CometChatUIKit.getLoggedInUser()
completion(success, nil)
case .failure(let error):
completion(false, error as? CometChatException)
}
}
}
}
// MARK: - Login with UID (Development)
func login(uid: String, completion: @escaping (User?, CometChatException?) -> Void) {
guard isInitialized else {
print("CometChat not initialized")
completion(nil, nil)
return
}
if let user = currentUser {
completion(user, nil)
return
}
CometChatUIKit.login(uid: uid) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let user):
self?.currentUser = user
self?.onLoginStateChanged?(user)
completion(user, nil)
case .onError(let error):
completion(nil, error)
@unknown default:
completion(nil, nil)
}
}
}
}
// MARK: - Login with Auth Token (Production)
func loginWithToken(_ authToken: String, completion: @escaping (User?, CometChatException?) -> Void) {
guard isInitialized else {
print("CometChat not initialized")
completion(nil, nil)
return
}
CometChatUIKit.login(authToken: authToken) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let user):
self?.currentUser = user
self?.onLoginStateChanged?(user)
completion(user, nil)
case .onError(let error):
completion(nil, error)
@unknown default:
completion(nil, nil)
}
}
}
}
// MARK: - Logout
func logout(completion: @escaping (Bool, CometChatException?) -> Void) {
guard let user = currentUser else {
completion(true, nil)
return
}
CometChatUIKit.logout(user: user) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success:
self?.currentUser = nil
self?.onLoginStateChanged?(nil)
completion(true, nil)
case .onError(let error):
completion(false, error)
@unknown default:
completion(false, nil)
}
}
}
}
}// Initialize
CometChatManager.shared.initialize(
appID: "YOUR_APP_ID",
authKey: "YOUR_AUTH_KEY",
region: "us"
) { success, error in
if success {
print("Initialized successfully")
} else if let error = error {
print("Init failed: \(error.errorDescription)")
}
}
// Login
CometChatManager.shared.login(uid: "cometchat-uid-1") { user, error in
if let user = user {
print("Logged in as: \(user.name ?? "")")
// Show chat UI
} else if let error = error {
print("Login failed: \(error.errorDescription)")
}
}
// Logout
CometChatManager.shared.logout { success, error in
if success {
print("Logged out")
} else if let error = error {
print("Logout failed: \(error.errorDescription)")
}
}CometChat iOS UI Kit uses CometChatTheme for styling. Configure it before showing any UI:
// Set primary color
CometChatTheme.primaryColor = UIColor.systemBlue
// Set background colors
CometChatTheme.backgroundColor01 = UIColor.systemBackground
CometChatTheme.backgroundColor02 = UIColor.secondarySystemBackground
// Set text colors
CometChatTheme.textColorPrimary = UIColor.label
CometChatTheme.textColorSecondary = UIColor.secondaryLabelEach component has a static style property:
// Conversations list style
CometChatConversations.style.backgroundColor = .systemBackground
CometChatConversations.style.titleColor = .label
// Message list style
CometChatMessageList.style.backgroundColor = .systemBackground
// Avatar style — cornerRadius is a CometChatCornerStyle. Its only value-
// taking init is the labeled designated init (the bare
// `CometChatCornerStyle(cornerRadius:)` belongs to the sibling
// CometChatCorner struct and will NOT compile here). Pass a value larger
// than half the avatar dimension for a circular look.
CometChatAvatar.style.backgroundColor = .systemGray5
CometChatAvatar.style.cornerRadius = CometChatCornerStyle(
topLeft: true, topRight: true, bottomLeft: true, bottomRight: true,
cornerRadius: 100
)CometChat automatically supports dark mode when using system colors:
CometChatTheme.primaryColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark
? UIColor.systemBlue
: UIColor.blue
}CometChat iOS UI Kit supports 20+ languages out of the box. The language is automatically detected from the device settings.
Arabic, Chinese (Simplified), Chinese (Traditional), Dutch, English, French, German, Hindi, Hungarian, Japanese, Korean, Lithuanian, Malay, Portuguese, Russian, Spanish, Swedish, Turkish
CometChatLocalize is a Bundle subclass that swaps the kit's .lproj lookup at runtime. The public API is locale-only:
CometChatLocalize.set(locale: .english) // enum value
CometChatLocalize.set(locale: "fr") // raw stringThere is no CometChatLocalize.set(key:value:) for ad-hoc key overrides — to customize specific strings, override them in your app's Localizable.strings file (the kit reads through the standard bundle lookup chain).
These are specific things NOT to do. Each one causes real bugs.
currentUser first. Concurrent login calls cause errors.CometChatManager.shared.logout() to clear the CometChat session.cometchat-ios-components skill before writing any code.Common types from CometChatSDK:
import CometChatSDK
// User — represents a chat user
let user: User
// Group — represents a chat group
let group: Group
// Conversation — wraps User or Group
let conversation: Conversation
// BaseMessage — base class for all messages
let message: BaseMessage
// TextMessage — a text message
let textMessage: TextMessage
// MediaMessage — image, video, audio, file
let mediaMessage: MediaMessage
// CustomMessage — custom data message
let customMessage: CustomMessage// Get a user by UID
CometChat.getUser(UID: "user-uid") { user in
print("User: \(user?.name ?? "")")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
// Get a group by GUID
CometChat.getGroup(GUID: "group-guid") { group in
print("Group: \(group?.name ?? "")")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}Every CometChat iOS integration requires:
# Podfile
pod 'CometChatUIKitSwift', '~> 5.1'This automatically includes:
CometChatSDK — Core SDK with types and methodsFor voice/video calls, add:
pod 'CometChatCallsSDK', '~> 5.0' # requires iOS 16+ deployment targetThe UI Kit automatically detects and enables calling features when the Calls SDK is present.
When the dispatcher's Step 3.1 sets customize=visual and the platform resolves to ios, skills runs `cometchat builder export --platform ios` — a single CLI command that downloads the canonical static template ZIP from preview.cometchat.com/downloads/cometchat-builder-ios.zip, fetches the per-builder settings JSON, applies F3 + F10 missing-field defaults, and writes 3 files to --output (default: CometChat/):
MessagesVC.swift — verbatim view controller composing header + list + composerThreadedMessagesVC.swift — verbatim helper VC (imported by MessagesVC)cometchat-builder-settings.json — envelope-shape JSON { builderId, settings: {...} } (the iOS canonical has no top-level name; CometChatBuilderSettings.loadFromJSON() reads only settings.*) (no sentinel — JSON forbids // comments)ℹ️ Version-history note (ENG-35337) — RESOLVED end-to-end; `pod 'CometChatBuilder'` (1.1.2) builds AND launches. A freshly-exported iOS builder ZIP was built end-to-end (pod 'CometChatBuilder'unversioned → resolves 1.1.2, with CometChatUIKitSwift 5.1.13 / CometChatSDK 4.1.3 / CometChatCallsSDK 5.0.0) → `BUILD SUCCEEDED`, then installed + launched clean on a physical iPhone (verified 2026-06-17 — the earlier sim-onlyEXCLUDED_ARCHSquirk was an environment issue, not a project defect). The currentThreadedMessagesVC.swiftno longer readsCometChatBuilderSettings.shared.layout.compactMessageComposer(grep-confirmed absent), so the older compile failure is gone — do NOT pin> 1.1.2(a1.1.3may not exist; pinning it would breakpod install). Only if you have an OLDER export whoseThreadedMessagesVC.swiftstill references.layout.compactMessageComposerand you hit"value of type 'Layout' has no member 'compactMessageComposer'", re-export from the dashboard (preferred) or pin a newerCometChatBuilderif one is published.
cometchat builder exportcometchat builder export --platform ios --jsonDefaults to --output CometChat/. Adjust the output if the customer's project uses a different chat-surface group name (e.g. --output Chat/ for projects that use a Chat group).
CometChatBuilderSettings.loadFromJSON() looks for the envelope shape — handing it the raw settings blob causes CometChatBuilderSettings.shared to fall back to defaults silently (no error logged). The CLI always writes the envelope, so this trap is closed.
Resync = re-run the same command with --force. See cometchat-core §11.6 for the resync contract.
builder export)| Path | Content |
|---|---|
CometChat/CometChatApp.swift | Optional, SwiftUI-host only. Skills-authored convenience wrapper that mounts CometChatConversations + pushes MessagesVC on tap. Feature flags read from CometChatBuilderSettings.shared. Skills emits this — not from the ZIP. The canonical (reference-app) entry is the UIKit SceneDelegate → FrameworkManager → HomeScreenViewController path, not this wrapper. |
| Path | Patch |
|---|---|
Podfile | Add pod 'CometChatBuilder', '> 1.1.2' (the canonical pod surfacing CometChatBuilderSettings.shared + loadFromJSON(); pin `> 1.1.2` — ≤1.1.2 lacks Layout.compactMessageComposer and fails to compile, see the build-breaker caveat above). Add pod 'CometChatUIKitSwift', '~> 5.1' if not already declared. SPM equivalent: .package(url: "https://github.com/cometchat/cometchat-builder-ios", from: "1.1.3") |
SceneDelegate.swift (UIKit — canonical) / App.swift (SwiftUI — alternative) | Add CometChatBuilderSettings.loadFromJSON() + theme application + CometChatUIKit.init(uiKitSettings:). For scene-based apps (the reference app and any project with a `SceneDelegate`), init in `scene(_:willConnectTo:)` — NOT in `AppDelegate.didFinishLaunchingWithOptions` (that runs before the scene/window exists). See init code below |
| App target | Toggle cometchat-builder-settings.json for target membership in Xcode (else loadFromJSON() silently returns defaults) |
Entry view — root UIViewController (UIKit) / ContentView.swift (SwiftUI) | Mount the chat surface per Step 3c placement (modal sheet, push-navigation destination, tab item, or embedded view) |
Info.plist | NSMicrophoneUsageDescription + NSCameraUsageDescription if CometChatBuilderSettings.shared.callFeatures has any enabled feature |
The reference app (CometChatBuilderSwift) is pure UIKit, scene-based: @main AppDelegate does nothing but vend the scene configuration, and SceneDelegate.scene(_:willConnectTo:) calls into a FrameworkManager.initialize(with:completion:) helper that loads settings → applies theme → inits the kit. Mirror that — do not init in AppDelegate.didFinishLaunchingWithOptions for a scene-based app (the window/scene doesn't exist yet there).
// SceneDelegate.swift
import UIKit
import CometChatBuilder
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard (scene as? UIWindowScene) != nil else { return }
FrameworkManager.initialize(with: scene) {
print("Framework initialized ✅")
}
}
}// FrameworkManager.swift — load settings → applyTheme → init kit
import UIKit
import CometChatUIKitSwift
import CometChatSDK
import CometChatBuilder
enum FrameworkManager {
static func initialize(with scene: UIScene, completion: @escaping () -> Void) {
// 1. Load builder settings from bundled JSON
CometChatBuilderSettings.loadFromJSON()
// 2. Apply builder theme tokens to the kit's global theme
applyTheme()
// 3. Standard UI Kit init (this file's §2 — credentials from Secrets.swift)
let settings = UIKitSettings()
.set(appID: Secrets.appID)
.set(authKey: Secrets.authKey)
.set(region: Secrets.region)
.subscribePresenceForAllUsers()
.build()
CometChatUIKit.init(uiKitSettings: settings) { _ in
// set the root view controller on `scene` here, then:
completion()
}
}
static func applyTheme() {
let color = CometChatBuilderSettings.shared.style.color
CometChatTheme.primaryColor = UIColor.dynamicColor(
lightModeColor: UIColor(hex: color.brandColor),
darkModeColor: UIColor(hex: color.brandColor)
)
CometChatTheme.textColorPrimary = UIColor.dynamicColor(
lightModeColor: UIColor(hex: color.primaryTextLight),
darkModeColor: UIColor(hex: color.primaryTextDark)
)
CometChatTheme.textColorSecondary = UIColor.dynamicColor(
lightModeColor: UIColor(hex: color.secondaryTextLight),
darkModeColor: UIColor(hex: color.secondaryTextDark)
)
// ⚠️ Map the builder's raw font label to a registered PostScript name
// BEFORE handing it to setFont — passing the raw value (e.g. "Roboto")
// does NOT resolve to a real font and silently falls back to the system font.
let rawFont = CometChatBuilderSettings.shared.style.typography.font
CometChatTypography.setFont(name: fontName(rawFont) ?? "")
}
/// Resolves a builder font label to its registered PostScript name.
/// (Verbatim from the reference app's FrameworkManager.fontName.)
static func fontName(_ name: String) -> String? {
switch name.lowercased() {
case "times new roman": return "TimesNewRomanPSMT"
case "arial": return "ArialMT"
case "roboto": return "Roboto-Regular"
case "inter": return "Inter-Regular"
default: return "\(name.capitalized)-Regular"
}
}
}Font trap (D4).style.typography.fontin the builder JSON is a human label like"Roboto"or"Arial"— not a PostScript font name. Passing it straight toCometChatTypography.setFont(name:)(e.g.setFont(name: settings.style.typography.font)) silently fails to load the font. Always route it through thefontName(_:)lookup above (or otherwise resolve to a registered PostScript name, and register any custom.ttf/.otfinInfo.plistunderUIAppFonts).
loadFromJSON() reads cometchat-builder-settings.json from the main bundle. If the file isn't a target member (Xcode → file inspector → Target Membership), CometChatBuilderSettings.shared silently falls back to defaults — this is the #1 integration bug. Sanity-check by printing CometChatBuilderSettings.shared.style.color.brandColor after loadFromJSON().
CometChatApp.swift below is a skills-authored convenience wrapper for teams who want to mount the chat surface inside a SwiftUI hierarchy. It is not the canonical entry point — the reference app is pure UIKit and routes from SceneDelegate → FrameworkManager → HomeScreenViewController (a UITabBarController). For UIKit projects, prefer the SceneDelegate path above and mount CometChatConversations() / HomeScreenViewController directly; use this SwiftUI wrapper only when the host app is SwiftUI.
// CometChat/CometChatApp.swift — skills-authored SwiftUI convenience wrapper (NOT the canonical app)
import SwiftUI
import UIKit
import CometChatBuilder
import CometChatUIKitSwift
import CometChatSDK
/// SwiftUI convenience wrapper around the chat surface (skills-authored, not from the ZIP).
/// Master/detail SwiftUI host: `CometChatConversations` at root, `MessagesVC` pushed on tap.
/// Feature visibility flags are read from `CometChatBuilderSettings.shared`.
public struct CometChatApp: View {
public init() {}
public var body: some View {
ConversationsContainer().ignoresSafeArea()
}
}
private struct ConversationsContainer: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UINavigationController {
let conversationsVC = CometChatConversations()
let core = CometChatBuilderSettings.shared.chatFeatures.coreMessagingExperience
conversationsVC.hideUserStatus = !core.userAndFriendsPresence
conversationsVC.hideReceipts = !core.messageDeliveryAndReadReceipts
let nav = UINavigationController(rootViewController: conversationsVC)
conversationsVC.set(onItemClick: { [weak nav] conversation, _ in
let vc = MessagesVC()
// Qualify with CometChatSDK. — this file imports SwiftUI, which also
// exports a `Group` type, so a bare `Group` is "ambiguous for type lookup"
// and won't compile. (User is unambiguous but qualified here for parity.)
if let user = conversation.conversationWith as? CometChatSDK.User { vc.user = user }
else if let group = conversation.conversationWith as? CometChatSDK.Group { vc.group = group }
nav?.pushViewController(vc, animated: true)
})
return nav
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}
private extension UIColor {
convenience init?(hex: String) {
let s = hex.replacingOccurrences(of: "#", with: "")
guard s.count == 6, let rgb = UInt32(s, radix: 16) else { return nil }
self.init(red: CGFloat((rgb >> 16) & 0xFF) / 255,
green: CGFloat((rgb >> 8) & 0xFF) / 255,
blue: CGFloat(rgb & 0xFF) / 255,
alpha: 1)
}
static func dynamicColor(lightModeColor: UIColor?, darkModeColor: UIColor?) -> UIColor {
UIColor { traits in
(traits.userInterfaceStyle == .dark ? darkModeColor : lightModeColor) ?? UIColor.label
}
}
}MessagesVC.swift is copied verbatim from CometChatBuilderSwift/BuilderApp/View Controllers/CometChat Components/MessagesVC.swift (inside the Ios Visual Builder ZIP at https://preview.cometchat.com/downloads/cometchat-builder-ios.zip). It composes header + list + composer in a UIViewController and wires reaction / thread / typing per CometChatBuilderSettings.shared.chatFeatures.*. Do not hand-roll this — the canonical file is the reference.
Secrets.swift is the credentials enum (enum Secrets { static let appID = "..."; static let region = "..."; static let authKey = "..." }) populated by Step 2c provision. Added to .gitignore. (Note: this is the convention for integrating into the user's OWN app. The dashboard-exported reference builder app instead ships AppConstants.swift — a public class AppConstants with mutable APP_ID/AUTH_KEY/REGION statics persisted to UserDefaults + a ChangeAppCredentialsVC for runtime entry; verified 2026-06-14 against a real exported ZIP. If you're editing the exported app directly, set creds in AppConstants.swift.)
UIKit (canonical) vs SwiftUI hosts. The reference app is pure UIKit: FrameworkManager.setRootViewController(...) (called from the init completion: above) sets either UINavigationController(rootViewController: HomeScreenViewController()) or a SplitViewController on iPad — see CometChatBuilderSwift/BuilderApp/SceneDelegate.swift + FrameworkManager.swift + View Controllers/HomeScreenViewController.swift (inside the Ios Visual Builder ZIP at https://preview.cometchat.com/downloads/cometchat-builder-ios.zip). The CometChatApp SwiftUI wrapper above is the alternative for SwiftUI-hosted apps; it wraps the same CometChatConversations surface in a UIViewControllerRepresentable.
If CometChatBuilderSettings.shared.callFeatures has any enabled feature:
cometchat-ios-calls)CometChatIncomingCall at app root (SwiftUI App's WindowGroup root, or UIKit's keyWindow.rootViewController overlay — the builder repo's BuilderApplication-equivalent pattern in SceneDelegate.swift is the reference)cometchat-ios-pushThe builder repo's HomeScreenViewController is a UITabBarController with up to 4 tabs (Chats / Calls / Users / Groups) driven by CometChatBuilderSettings.shared.layout.tabs. Skills' thin wrapper emits a single conversations surface, not the tabbed shape. Theme color + typography + chat-feature toggles ARE honored. To get the tabbed shape, copy HomeScreenViewController.swift from the builder repo instead of the wrapper template above.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.