cometchat-ios-placement — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-ios-placement (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 ~> 5view controllers +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 symbols against the installed package/source before relying on them.
This skill teaches WHERE to place CometChat components in your iOS app. It covers navigation patterns, tab bar integration, modal presentations, and embedded views for different use cases.
The most common pattern for messaging apps. Push conversations onto a navigation stack.
┌─────────────────────────────────────────────────────────────┐
│ UINavigationController │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ CometChatConversations ││
│ │ ┌─────────────────────────────────────────────────────┐││
│ │ │ Conversation 1 │││
│ │ │ Conversation 2 ──────────────────────────────────►│││
│ │ │ Conversation 3 │││
│ │ └─────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MessagesViewController (pushed) │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ CometChatMessageHeader ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ CometChatMessageList ││
│ │ ││
│ │ ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ CometChatMessageComposer ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘// SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
// Create conversations list
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak conversations] conversation, _ in
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
if let user = conversation.conversationWith as? User {
messagesVC.user = user
} else if let group = conversation.conversationWith as? Group {
messagesVC.group = group
}
conversations?.navigationController?.pushViewController(messagesVC, animated: true)
})
// Wrap in navigation controller
let navController = UINavigationController(rootViewController: conversations)
window.rootViewController = navController
self.window = window
window.makeKeyAndVisible()
}import SwiftUI
import CometChatUIKitSwift
import CometChatSDK
struct ChatNavigationView: View {
@State private var selectedConversation: Conversation?
@State private var showMessages = false
var body: some View {
NavigationStack {
ConversationsListView(selectedConversation: $selectedConversation)
.navigationDestination(isPresented: $showMessages) {
if let conversation = selectedConversation {
MessagesView(conversation: conversation)
}
}
.onChange(of: selectedConversation) { newValue in
showMessages = newValue != nil
}
}
}
}
struct ConversationsListView: UIViewControllerRepresentable {
@Binding var selectedConversation: Conversation?
func makeUIViewController(context: Context) -> CometChatConversations {
let conversations = CometChatConversations()
conversations.set(onItemClick: { conversation, _ in
selectedConversation = conversation
})
return conversations
}
func updateUIViewController(_ uiViewController: CometChatConversations, context: Context) {}
}For apps where chat is one of several main features.
┌─────────────────────────────────────────────────────────────┐
│ │
│ Content Area │
│ │
│ │
│ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Home │ │Chats│ │Users│ │Groups│ │Calls│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────────────────────────────────────┘import UIKit
import CometChatUIKitSwift
import CometChatSDK
class MainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupTabs()
}
private func setupTabs() {
// Home tab (your existing content)
let homeVC = HomeViewController()
homeVC.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
// Chats tab
let chatsVC = createChatsTab()
let chatsNav = UINavigationController(rootViewController: chatsVC)
chatsNav.tabBarItem = UITabBarItem(
title: "Chats",
image: UIImage(systemName: "message"),
selectedImage: UIImage(systemName: "message.fill")
)
// Users tab
let usersVC = CometChatUsers()
usersVC.set(onItemClick: { [weak self] user, _ in
self?.openMessages(with: user)
})
let usersNav = UINavigationController(rootViewController: usersVC)
usersNav.tabBarItem = UITabBarItem(
title: "Users",
image: UIImage(systemName: "person.2"),
selectedImage: UIImage(systemName: "person.2.fill")
)
// Groups tab
let groupsVC = CometChatGroups()
groupsVC.set(onItemClick: { [weak self] group, _ in
self?.openMessages(with: group)
})
let groupsNav = UINavigationController(rootViewController: groupsVC)
groupsNav.tabBarItem = UITabBarItem(
title: "Groups",
image: UIImage(systemName: "person.3"),
selectedImage: UIImage(systemName: "person.3.fill")
)
var tabs: [UIViewController] = [
UINavigationController(rootViewController: homeVC),
chatsNav,
usersNav,
groupsNav
]
// Calls tab - only available if CometChatCallsSDK is installed
#if canImport(CometChatCallsSDK)
let callsVC = CometChatCallLogs()
let callsNav = UINavigationController(rootViewController: callsVC)
callsNav.tabBarItem = UITabBarItem(
title: "Calls",
image: UIImage(systemName: "phone"),
selectedImage: UIImage(systemName: "phone.fill")
)
tabs.append(callsNav)
#endif
viewControllers = tabs
}
private func createChatsTab() -> CometChatConversations {
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak self] conversation, _ in
if let user = conversation.conversationWith as? User {
self?.openMessages(with: user)
} else if let group = conversation.conversationWith as? Group {
self?.openMessages(with: group)
}
})
return conversations
}
private func openMessages(with user: User) {
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
messagesVC.user = user
messagesVC.hidesBottomBarWhenPushed = true
if let navController = selectedViewController as? UINavigationController {
navController.pushViewController(messagesVC, animated: true)
}
}
private func openMessages(with group: Group) {
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
messagesVC.group = group
messagesVC.hidesBottomBarWhenPushed = true
if let navController = selectedViewController as? UINavigationController {
navController.pushViewController(messagesVC, animated: true)
}
}
}struct MainTabView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(0)
ChatNavigationView()
.tabItem {
Label("Chats", systemImage: "message")
}
.tag(1)
UsersNavigationView()
.tabItem {
Label("Users", systemImage: "person.2")
}
.tag(2)
GroupsNavigationView()
.tabItem {
Label("Groups", systemImage: "person.3")
}
.tag(3)
CallsNavigationView()
.tabItem {
Label("Calls", systemImage: "phone")
}
.tag(4)
}
}
}For apps where chat is a secondary feature, presented modally.
┌─────────────────────────────────────────────────────────────┐
│ Your App Content │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Product Details ││
│ │ ││
│ │ ┌─────────────────────────────────────────────────────┐││
│ │ │ [Chat with Seller] ◄─────────────────────────────│││
│ │ └─────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
│
▼ (modal presentation)
┌─────────────────────────────────────────────────────────────┐
│ ┌─────────────────────────────────────────────────────────┐│
│ │ [X] Chat with John ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ ││
│ │ Messages ││
│ │ ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ Composer ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘class ProductDetailViewController: UIViewController {
var sellerUID: String?
@IBAction func chatWithSellerTapped(_ sender: UIButton) {
guard let sellerUID = sellerUID else { return }
// Fetch the seller user
CometChat.getUser(UID: sellerUID) { [weak self] user in
guard let user = user else { return }
DispatchQueue.main.async {
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
messagesVC.user = user
let navController = UINavigationController(rootViewController: messagesVC)
navController.modalPresentationStyle = .pageSheet
// Add close button
messagesVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .close,
target: self,
action: #selector(self?.dismissChat)
)
self?.present(navController, animated: true)
}
} onError: { error in
print("Error fetching user: \(error?.errorDescription ?? "")")
}
}
@objc private func dismissChat() {
dismiss(animated: true)
}
}struct ProductDetailView: View {
let sellerUID: String
@State private var showChat = false
@State private var seller: User?
var body: some View {
VStack {
// Product details...
Button("Chat with Seller") {
fetchSellerAndShowChat()
}
.buttonStyle(.borderedProminent)
}
.sheet(isPresented: $showChat) {
if let seller = seller {
NavigationStack {
MessagesView(user: seller)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Close") {
showChat = false
}
}
}
}
}
}
}
private func fetchSellerAndShowChat() {
CometChat.getUser(UID: sellerUID) { user in
DispatchQueue.main.async {
self.seller = user
self.showChat = true
}
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
}
}For support chat or quick access to messages.
┌─────────────────────────────────────────────────────────────┐
│ Your App Content │
│ │
│ │
│ │
│ │
│ │
│ │
│ ┌─────┐ │
│ │ 💬 │ │
│ └─────┘ │
└─────────────────────────────────────────────────────────────┘class FloatingChatButton: UIButton {
private var unreadCount: Int = 0 {
didSet {
updateBadge()
}
}
private lazy var badgeLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .systemRed
label.textColor = .white
label.font = .systemFont(ofSize: 12, weight: .bold)
label.textAlignment = .center
label.layer.cornerRadius = 10
label.clipsToBounds = true
label.isHidden = true
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupButton()
}
private func setupButton() {
backgroundColor = .systemBlue
layer.cornerRadius = 28
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 4)
layer.shadowRadius = 8
layer.shadowOpacity = 0.3
setImage(UIImage(systemName: "message.fill"), for: .normal)
tintColor = .white
addSubview(badgeLabel)
badgeLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
badgeLabel.topAnchor.constraint(equalTo: topAnchor, constant: -5),
badgeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 5),
badgeLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
badgeLabel.heightAnchor.constraint(equalToConstant: 20)
])
}
private func updateBadge() {
badgeLabel.isHidden = unreadCount == 0
badgeLabel.text = unreadCount > 99 ? "99+" : "\(unreadCount)"
}
func setUnreadCount(_ count: Int) {
unreadCount = count
}
}
// Usage in a view controller
class MainViewController: UIViewController {
private lazy var chatButton: FloatingChatButton = {
let button = FloatingChatButton()
button.addTarget(self, action: #selector(openChat), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupFloatingButton()
}
private func setupFloatingButton() {
view.addSubview(chatButton)
chatButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
chatButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
chatButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
chatButton.widthAnchor.constraint(equalToConstant: 56),
chatButton.heightAnchor.constraint(equalToConstant: 56)
])
}
@objc private func openChat() {
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak self] conversation, _ in
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
if let user = conversation.conversationWith as? User {
messagesVC.user = user
} else if let group = conversation.conversationWith as? Group {
messagesVC.group = group
}
conversations.navigationController?.pushViewController(messagesVC, animated: true)
})
let navController = UINavigationController(rootViewController: conversations)
navController.modalPresentationStyle = .pageSheet
conversations.navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .close,
target: self,
action: #selector(dismissChat)
)
present(navController, animated: true)
}
@objc private func dismissChat() {
dismiss(animated: true)
}
}For iPad apps with master-detail layout.
┌─────────────────────────────────────────────────────────────────────────────┐
│ ┌─────────────────────────┐ ┌─────────────────────────────────────────────┐│
│ │ Conversations │ │ Messages ││
│ │ ┌─────────────────────┐│ │ ┌─────────────────────────────────────────┐││
│ │ │ John Doe ││ │ │ Header │││
│ │ │ Jane Smith ◄──────┼┼──┼──┤─────────────────────────────────────────│││
│ │ │ Team Chat ││ │ │ Message List │││
│ │ │ ││ │ │ │││
│ │ │ ││ │ │ │││
│ │ │ ││ │ ├─────────────────────────────────────────┤││
│ │ │ ││ │ │ Composer │││
│ │ └─────────────────────┘│ │ └─────────────────────────────────────────┘││
│ └─────────────────────────┘ └─────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘class ChatSplitViewController: UISplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
// Primary: Conversations
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak self] conversation, _ in
self?.showMessages(for: conversation)
})
let primaryNav = UINavigationController(rootViewController: conversations)
// Secondary: Empty state or messages
let emptyVC = EmptyStateViewController()
let secondaryNav = UINavigationController(rootViewController: emptyVC)
viewControllers = [primaryNav, secondaryNav]
}
private func showMessages(for conversation: Conversation) {
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
if let user = conversation.conversationWith as? User {
messagesVC.user = user
} else if let group = conversation.conversationWith as? Group {
messagesVC.group = group
}
let secondaryNav = UINavigationController(rootViewController: messagesVC)
showDetailViewController(secondaryNav, sender: self)
}
}
class EmptyStateViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let label = UILabel()
label.text = "Select a conversation"
label.textColor = .secondaryLabel
label.textAlignment = .center
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}For embedding chat in a portion of the screen.
┌─────────────────────────────────────────────────────────────┐
│ Your App Header │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Your Content ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Embedded Chat (CometChatMessageList + Composer) ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘class EmbeddedChatViewController: UIViewController {
private var supportUser: User?
private lazy var chatContainer: UIView = {
let view = UIView()
view.backgroundColor = .systemBackground
view.layer.cornerRadius = 12
view.clipsToBounds = true
return view
}()
private lazy var messageList = CometChatMessageList()
private lazy var messageComposer = CometChatMessageComposer()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
fetchSupportUser()
}
private func setupUI() {
view.addSubview(chatContainer)
chatContainer.translatesAutoresizingMaskIntoConstraints = false
chatContainer.addSubview(messageList)
chatContainer.addSubview(messageComposer)
messageList.translatesAutoresizingMaskIntoConstraints = false
messageComposer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// Chat container takes bottom half of screen
chatContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
chatContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
chatContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
chatContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4),
// Message list
messageList.topAnchor.constraint(equalTo: chatContainer.topAnchor),
messageList.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
messageList.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
messageList.bottomAnchor.constraint(equalTo: messageComposer.topAnchor),
// Composer
messageComposer.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
messageComposer.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
messageComposer.bottomAnchor.constraint(equalTo: chatContainer.bottomAnchor)
])
}
private func fetchSupportUser() {
CometChat.getUser(UID: "support-agent") { [weak self] user in
guard let user = user else { return }
DispatchQueue.main.async {
self?.supportUser = user
self?.messageList.set(user: user)
self?.messageList.set(controller: self) // mandatory — see cometchat-ios-components § 4/§ 6
self?.messageComposer.set(user: user)
self?.messageComposer.set(controller: self) // mandatory — enables attachments/sheets
}
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
}
}hidesBottomBarWhenPushed = true~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.