| name | cometchat-ios-placement |
| description | WHERE to put CometChat in your iOS app — navigation patterns, tab bars, modals, and embedded views. |
| license | MIT |
| compatibility | CometChatUIKitSwift ^5; iOS 13+ |
| metadata | {"author":"CometChat","version":"3.0.0","tags":"chat cometchat ios placement navigation tabs modal patterns"} |
Ground truth: CometChatUIKitSwift ~> 5 view 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.
Purpose
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.
1. Navigation Stack Pattern
The most common pattern for messaging apps. Push conversations onto a navigation stack.
Basic Navigation Flow
┌─────────────────────────────────────────────────────────────┐
│ UINavigationController │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ CometChatConversations ││
│ │ ┌─────────────────────────────────────────────────────┐││
│ │ │ Conversation 1 │││
│ │ │ Conversation 2 ──────────────────────────────────►│││
│ │ │ Conversation 3 │││
│ │ └─────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MessagesViewController (pushed) │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ CometChatMessageHeader ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ CometChatMessageList ││
│ │ ││
│ │ ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ CometChatMessageComposer ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Implementation (UIKit)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak conversations] conversation, _ in
let messagesVC = MessagesVC()
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: )
})
navController (rootViewController: conversations)
window.rootViewController navController
.window window
window.makeKeyAndVisible()
}
Implementation (SwiftUI)
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,
selectedConversation conversation
})
conversations
}
( : , : ) {}
}
2. Tab Bar Pattern
For apps where chat is one of several main features.
Tab Layout
┌─────────────────────────────────────────────────────────────┐
│ │
│ Content Area │
│ │
│ │
│ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Home │ │Chats│ │Users│ │Groups│ │Calls│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────────────────────────────────────┘
Implementation (UIKit)
import UIKit
import CometChatUIKitSwift
import CometChatSDK
class MainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupTabs()
}
private func setupTabs() {
let homeVC = HomeViewController()
homeVC.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let chatsVC = createChatsTab()
let chatsNav = UINavigationController(rootViewController: chatsVC)
chatsNav.tabBarItem = UITabBarItem(
title: "Chats",
image: UIImage(systemName: "message"),
selectedImage: UIImage(systemName: "message.fill")
)
let usersVC = CometChatUsers()
usersVC.set(onItemClick: { [weak self] user, _ in
.openMessages(with: user)
})
usersNav (rootViewController: usersVC)
usersNav.tabBarItem (
title: ,
image: (systemName: ),
selectedImage: (systemName: )
)
groupsVC ()
groupsVC.set(onItemClick: { [ ] group,
.openMessages(with: group)
})
groupsNav (rootViewController: groupsVC)
groupsNav.tabBarItem (
title: ,
image: (systemName: ),
selectedImage: (systemName: )
)
tabs: [] [
(rootViewController: homeVC),
chatsNav,
usersNav,
groupsNav
]
canImport()
callsVC ()
callsNav (rootViewController: callsVC)
callsNav.tabBarItem (
title: ,
image: (systemName: ),
selectedImage: (systemName: )
)
tabs.append(callsNav)
viewControllers tabs
}
() -> {
conversations ()
conversations.set(onItemClick: { [ ] conversation,
user conversation.conversationWith {
.openMessages(with: user)
} group conversation.conversationWith {
.openMessages(with: group)
}
})
conversations
}
( : ) {
messagesVC ()
messagesVC.user user
messagesVC.hidesBottomBarWhenPushed
navController selectedViewController {
navController.pushViewController(messagesVC, animated: )
}
}
( : ) {
messagesVC ()
messagesVC.group group
messagesVC.hidesBottomBarWhenPushed
navController selectedViewController {
navController.pushViewController(messagesVC, animated: )
}
}
}
Implementation (SwiftUI)
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)
}
}
}
3. Modal Presentation Pattern
For apps where chat is a secondary feature, presented modally.
Modal Layout
┌─────────────────────────────────────────────────────────────┐
│ Your App Content │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Product Details ││
│ │ ││
│ │ ┌─────────────────────────────────────────────────────┐││
│ │ │ [Chat with Seller] ◄─────────────────────────────│││
│ │ └─────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
│
▼ (modal presentation)
┌─────────────────────────────────────────────────────────────┐
│ ┌─────────────────────────────────────────────────────────┐│
│ │ [X] Chat with John ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ ││
│ │ Messages ││
│ │ ││
│ ├─────────────────────────────────────────────────────────┤│
│ │ Composer ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Implementation (UIKit)
class ProductDetailViewController: UIViewController {
var sellerUID: String?
@IBAction func chatWithSellerTapped(_ sender: UIButton) {
guard let sellerUID = sellerUID else { return }
CometChat.getUser(UID: sellerUID) { [weak self] user in
guard let user = user else { return }
DispatchQueue.main.async {
let messagesVC = MessagesVC()
messagesVC.user = user
let navController = UINavigationController(rootViewController: messagesVC)
navController.modalPresentationStyle = .pageSheet
messagesVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .close,
target: self,
action: #selector(self?.dismissChat)
)
.present(navController, animated: )
}
} onError: { error
()
}
}
() {
dismiss(animated: )
}
}
Implementation (SwiftUI)
struct ProductDetailView: View {
let sellerUID: String
@State private var showChat = false
@State private var seller: User?
var body: some View {
VStack {
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
()
}
}
}
4. Floating Button Pattern
For support chat or quick access to messages.
Floating Button Layout
┌─────────────────────────────────────────────────────────────┐
│ Your App Content │
│ │
│ │
│ │
│ │
│ │
│ │
│ ┌─────┐ │
│ │ 💬 │ │
│ └─────┘ │
└─────────────────────────────────────────────────────────────┘
Implementation (UIKit)
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 = .black.cgColor
layer.shadowOffset (width: , height: )
layer.shadowRadius
layer.shadowOpacity
setImage((systemName: ), for: .normal)
tintColor .white
addSubview(badgeLabel)
badgeLabel.translatesAutoresizingMaskIntoConstraints
.activate([
badgeLabel.topAnchor.constraint(equalTo: topAnchor, constant: ),
badgeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: ),
badgeLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: ),
badgeLabel.heightAnchor.constraint(equalToConstant: )
])
}
() {
badgeLabel.isHidden unreadCount
badgeLabel.text unreadCount :
}
( : ) {
unreadCount count
}
}
: {
chatButton: {
button ()
button.addTarget(, action: (openChat), for: .touchUpInside)
button
}()
() {
.viewDidLoad()
setupFloatingButton()
}
() {
view.addSubview(chatButton)
chatButton.translatesAutoresizingMaskIntoConstraints
.activate([
chatButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: ),
chatButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: ),
chatButton.widthAnchor.constraint(equalToConstant: ),
chatButton.heightAnchor.constraint(equalToConstant: )
])
}
() {
conversations ()
conversations.set(onItemClick: { [ ] conversation,
messagesVC ()
user conversation.conversationWith {
messagesVC.user user
} group conversation.conversationWith {
messagesVC.group group
}
conversations.navigationController.pushViewController(messagesVC, animated: )
})
navController (rootViewController: conversations)
navController.modalPresentationStyle .pageSheet
conversations.navigationItem.leftBarButtonItem (
barButtonSystemItem: .close,
target: ,
action: (dismissChat)
)
present(navController, animated: )
}
() {
dismiss(animated: )
}
}
5. Split View Pattern (iPad)
For iPad apps with master-detail layout.
Split View Layout
┌─────────────────────────────────────────────────────────────────────────────┐
│ ┌─────────────────────────┐ ┌─────────────────────────────────────────────┐│
│ │ Conversations │ │ Messages ││
│ │ ┌─────────────────────┐│ │ ┌─────────────────────────────────────────┐││
│ │ │ John Doe ││ │ │ Header │││
│ │ │ Jane Smith ◄──────┼┼──┼──┤─────────────────────────────────────────│││
│ │ │ Team Chat ││ │ │ Message List │││
│ │ │ ││ │ │ │││
│ │ │ ││ │ │ │││
│ │ │ ││ │ ├─────────────────────────────────────────┤││
│ │ │ ││ │ │ Composer │││
│ │ └─────────────────────┘│ │ └─────────────────────────────────────────┘││
│ └─────────────────────────┘ └─────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
Implementation (UIKit)
class ChatSplitViewController: UISplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
let conversations = CometChatConversations()
conversations.set(onItemClick: { [weak self] conversation, _ in
self?.showMessages(for: conversation)
})
let primaryNav = UINavigationController(rootViewController: conversations)
let emptyVC = EmptyStateViewController()
let secondaryNav = UINavigationController(rootViewController: emptyVC)
viewControllers = [primaryNav, secondaryNav]
}
private func showMessages(for conversation: Conversation) {
let messagesVC = MessagesVC()
if let user = conversation.conversationWith as? {
messagesVC.user user
} group conversation.conversationWith {
messagesVC.group group
}
secondaryNav (rootViewController: messagesVC)
showDetailViewController(secondaryNav, sender: )
}
}
: {
() {
.viewDidLoad()
view.backgroundColor .systemBackground
label ()
label.text
label.textColor .secondaryLabel
label.textAlignment .center
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints
.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
6. Embedded View Pattern
For embedding chat in a portion of the screen.
Embedded Layout
┌─────────────────────────────────────────────────────────────┐
│ Your App Header │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Your Content ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Embedded Chat (CometChatMessageList + Composer) ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Implementation (UIKit)
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([
chatContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ),
chatContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: ),
chatContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: ),
chatContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: ),
messageList.topAnchor.constraint(equalTo: chatContainer.topAnchor),
messageList.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
messageList.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
messageList.bottomAnchor.constraint(equalTo: messageComposer.topAnchor),
messageComposer.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
messageComposer.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
messageComposer.bottomAnchor.constraint(equalTo: chatContainer.bottomAnchor)
])
}
() {
.getUser(UID: ) { [ ] user
user user { }
.main.async {
.supportUser user
.messageList.set(user: user)
.messageList.set(controller: )
.messageComposer.set(user: user)
.messageComposer.set(controller: )
}
} onError: { error
()
}
}
}
Best Practices
- Always wrap in UINavigationController when presenting CometChat view controllers
- Hide tab bar when pushing messages using
hidesBottomBarWhenPushed = true
- Handle keyboard properly — CometChat components handle this automatically
- Support both orientations — components adapt to orientation changes
- Test on iPad — use split view for better iPad experience
- Handle deep links — navigate to specific conversations from push notifications