@cometchat/skills 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -17
- package/bin/install.js +389 -43
- package/package.json +19 -9
- package/skills/cometchat/SKILL.md +1066 -265
- package/skills/cometchat-android-v5/SKILL.md +120 -0
- package/skills/cometchat-android-v5-components/SKILL.md +430 -0
- package/skills/cometchat-android-v5-core/SKILL.md +468 -0
- package/skills/cometchat-android-v5-customization/SKILL.md +305 -0
- package/skills/cometchat-android-v5-extensions/SKILL.md +124 -0
- package/skills/cometchat-android-v5-features/SKILL.md +130 -0
- package/skills/cometchat-android-v5-placement/SKILL.md +276 -0
- package/skills/cometchat-android-v5-production/SKILL.md +94 -0
- package/skills/cometchat-android-v5-push/SKILL.md +620 -0
- package/skills/cometchat-android-v5-testing/SKILL.md +187 -0
- package/skills/cometchat-android-v5-theming/SKILL.md +166 -0
- package/skills/cometchat-android-v5-troubleshooting/SKILL.md +73 -0
- package/skills/cometchat-android-v6/SKILL.md +107 -0
- package/skills/cometchat-android-v6-builder-settings/SKILL.md +145 -0
- package/skills/cometchat-android-v6-compose-components/SKILL.md +202 -0
- package/skills/cometchat-android-v6-compose-customization/SKILL.md +280 -0
- package/skills/cometchat-android-v6-compose-placement/SKILL.md +213 -0
- package/skills/cometchat-android-v6-compose-theming/SKILL.md +224 -0
- package/skills/cometchat-android-v6-core/SKILL.md +354 -0
- package/skills/cometchat-android-v6-events/SKILL.md +212 -0
- package/skills/cometchat-android-v6-extensions/SKILL.md +191 -0
- package/skills/cometchat-android-v6-features/SKILL.md +130 -0
- package/skills/cometchat-android-v6-kotlin-components/SKILL.md +197 -0
- package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +268 -0
- package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +248 -0
- package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +202 -0
- package/skills/cometchat-android-v6-production/SKILL.md +187 -0
- package/skills/cometchat-android-v6-push/SKILL.md +274 -0
- package/skills/cometchat-android-v6-testing/SKILL.md +279 -0
- package/skills/cometchat-android-v6-troubleshooting/SKILL.md +249 -0
- package/skills/cometchat-angular-components/SKILL.md +706 -0
- package/skills/cometchat-angular-core/SKILL.md +444 -0
- package/skills/cometchat-angular-customization/SKILL.md +424 -0
- package/skills/cometchat-angular-features/SKILL.md +447 -0
- package/skills/cometchat-angular-patterns/SKILL.md +447 -0
- package/skills/cometchat-angular-placement/SKILL.md +685 -0
- package/skills/cometchat-angular-production/SKILL.md +415 -0
- package/skills/cometchat-angular-theming/SKILL.md +365 -0
- package/skills/cometchat-angular-troubleshooting/SKILL.md +329 -0
- package/skills/cometchat-astro-patterns/SKILL.md +3 -3
- package/skills/cometchat-components/SKILL.md +11 -0
- package/skills/cometchat-core/SKILL.md +11 -6
- package/skills/cometchat-customization/references/component-catalog.md +1 -1
- package/skills/cometchat-features/SKILL.md +176 -108
- package/skills/cometchat-flutter-v5/SKILL.md +167 -0
- package/skills/cometchat-flutter-v5-calls/SKILL.md +192 -0
- package/skills/cometchat-flutter-v5-conversations/SKILL.md +117 -0
- package/skills/cometchat-flutter-v5-core/SKILL.md +221 -0
- package/skills/cometchat-flutter-v5-customization/SKILL.md +227 -0
- package/skills/cometchat-flutter-v5-events/SKILL.md +155 -0
- package/skills/cometchat-flutter-v5-messages/SKILL.md +258 -0
- package/skills/cometchat-flutter-v5-production/SKILL.md +207 -0
- package/skills/cometchat-flutter-v5-push/SKILL.md +284 -0
- package/skills/cometchat-flutter-v5-theming/SKILL.md +160 -0
- package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +129 -0
- package/skills/cometchat-flutter-v5-users-groups/SKILL.md +151 -0
- package/skills/cometchat-flutter-v6/SKILL.md +445 -0
- package/skills/cometchat-flutter-v6-calls/SKILL.md +333 -0
- package/skills/cometchat-flutter-v6-components/SKILL.md +365 -0
- package/skills/cometchat-flutter-v6-conversations/SKILL.md +210 -0
- package/skills/cometchat-flutter-v6-core/SKILL.md +305 -0
- package/skills/cometchat-flutter-v6-customization/SKILL.md +666 -0
- package/skills/cometchat-flutter-v6-events/SKILL.md +216 -0
- package/skills/cometchat-flutter-v6-features/SKILL.md +406 -0
- package/skills/cometchat-flutter-v6-messages/SKILL.md +333 -0
- package/skills/cometchat-flutter-v6-migration/SKILL.md +381 -0
- package/skills/cometchat-flutter-v6-placement/SKILL.md +398 -0
- package/skills/cometchat-flutter-v6-production/SKILL.md +632 -0
- package/skills/cometchat-flutter-v6-theming/SKILL.md +260 -0
- package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +726 -0
- package/skills/cometchat-flutter-v6-users-groups/SKILL.md +216 -0
- package/skills/cometchat-ios/SKILL.md +323 -0
- package/skills/cometchat-ios-components/SKILL.md +651 -0
- package/skills/cometchat-ios-core/SKILL.md +822 -0
- package/skills/cometchat-ios-customization/SKILL.md +825 -0
- package/skills/cometchat-ios-features/SKILL.md +725 -0
- package/skills/cometchat-ios-placement/SKILL.md +750 -0
- package/skills/cometchat-ios-production/SKILL.md +730 -0
- package/skills/cometchat-ios-push/SKILL.md +582 -0
- package/skills/cometchat-ios-theming/SKILL.md +631 -0
- package/skills/cometchat-ios-troubleshooting/SKILL.md +983 -0
- package/skills/cometchat-native-bare-patterns/SKILL.md +470 -0
- package/skills/cometchat-native-components/SKILL.md +594 -0
- package/skills/cometchat-native-core/SKILL.md +407 -0
- package/skills/cometchat-native-customization/SKILL.md +594 -0
- package/skills/cometchat-native-expo-patterns/SKILL.md +485 -0
- package/skills/cometchat-native-features/SKILL.md +608 -0
- package/skills/cometchat-native-placement/SKILL.md +500 -0
- package/skills/cometchat-native-production/SKILL.md +694 -0
- package/skills/cometchat-native-push/SKILL.md +593 -0
- package/skills/cometchat-native-testing/SKILL.md +583 -0
- package/skills/cometchat-native-theming/SKILL.md +656 -0
- package/skills/cometchat-native-troubleshooting/SKILL.md +433 -0
- package/skills/cometchat-nextjs-patterns/SKILL.md +5 -5
- package/skills/cometchat-production/SKILL.md +60 -0
- package/skills/cometchat-react-router-patterns/SKILL.md +23 -21
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cometchat-ios-placement
|
|
3
|
+
description: "WHERE to put CometChat in your iOS app — navigation patterns, tab bars, modals, and embedded views."
|
|
4
|
+
license: "MIT"
|
|
5
|
+
compatibility: "CometChatUIKitSwift ^5; iOS 13+"
|
|
6
|
+
allowed-tools: "executeBash, readFile, fileSearch, listDirectory"
|
|
7
|
+
metadata:
|
|
8
|
+
author: "CometChat"
|
|
9
|
+
version: "3.0.0"
|
|
10
|
+
tags: "chat cometchat ios placement navigation tabs modal patterns"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Navigation Stack Pattern
|
|
20
|
+
|
|
21
|
+
The most common pattern for messaging apps. Push conversations onto a navigation stack.
|
|
22
|
+
|
|
23
|
+
### Basic Navigation Flow
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
27
|
+
│ UINavigationController │
|
|
28
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
29
|
+
│ │ CometChatConversations ││
|
|
30
|
+
│ │ ┌─────────────────────────────────────────────────────┐││
|
|
31
|
+
│ │ │ Conversation 1 │││
|
|
32
|
+
│ │ │ Conversation 2 ──────────────────────────────────►│││
|
|
33
|
+
│ │ │ Conversation 3 │││
|
|
34
|
+
│ │ └─────────────────────────────────────────────────────┘││
|
|
35
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ MessagesViewController (pushed) │
|
|
41
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
42
|
+
│ │ CometChatMessageHeader ││
|
|
43
|
+
│ ├─────────────────────────────────────────────────────────┤│
|
|
44
|
+
│ │ CometChatMessageList ││
|
|
45
|
+
│ │ ││
|
|
46
|
+
│ │ ││
|
|
47
|
+
│ ├─────────────────────────────────────────────────────────┤│
|
|
48
|
+
│ │ CometChatMessageComposer ││
|
|
49
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
50
|
+
└─────────────────────────────────────────────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Implementation (UIKit)
|
|
54
|
+
|
|
55
|
+
```swift
|
|
56
|
+
// SceneDelegate.swift
|
|
57
|
+
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
|
58
|
+
guard let windowScene = (scene as? UIWindowScene) else { return }
|
|
59
|
+
|
|
60
|
+
let window = UIWindow(windowScene: windowScene)
|
|
61
|
+
|
|
62
|
+
// Create conversations list
|
|
63
|
+
let conversations = CometChatConversations()
|
|
64
|
+
conversations.set(onItemClick: { [weak conversations] conversation, _ in
|
|
65
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
66
|
+
if let user = conversation.conversationWith as? User {
|
|
67
|
+
messagesVC.set(user: user)
|
|
68
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
69
|
+
messagesVC.set(group: group)
|
|
70
|
+
}
|
|
71
|
+
conversations?.navigationController?.pushViewController(messagesVC, animated: true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Wrap in navigation controller
|
|
75
|
+
let navController = UINavigationController(rootViewController: conversations)
|
|
76
|
+
|
|
77
|
+
window.rootViewController = navController
|
|
78
|
+
self.window = window
|
|
79
|
+
window.makeKeyAndVisible()
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Implementation (SwiftUI)
|
|
84
|
+
|
|
85
|
+
```swift
|
|
86
|
+
import SwiftUI
|
|
87
|
+
import CometChatUIKitSwift
|
|
88
|
+
import CometChatSDK
|
|
89
|
+
|
|
90
|
+
struct ChatNavigationView: View {
|
|
91
|
+
@State private var selectedConversation: Conversation?
|
|
92
|
+
@State private var showMessages = false
|
|
93
|
+
|
|
94
|
+
var body: some View {
|
|
95
|
+
NavigationStack {
|
|
96
|
+
ConversationsListView(selectedConversation: $selectedConversation)
|
|
97
|
+
.navigationDestination(isPresented: $showMessages) {
|
|
98
|
+
if let conversation = selectedConversation {
|
|
99
|
+
MessagesView(conversation: conversation)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
.onChange(of: selectedConversation) { newValue in
|
|
103
|
+
showMessages = newValue != nil
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
struct ConversationsListView: UIViewControllerRepresentable {
|
|
110
|
+
@Binding var selectedConversation: Conversation?
|
|
111
|
+
|
|
112
|
+
func makeUIViewController(context: Context) -> CometChatConversations {
|
|
113
|
+
let conversations = CometChatConversations()
|
|
114
|
+
conversations.onItemClick = { conversation, _ in
|
|
115
|
+
selectedConversation = conversation
|
|
116
|
+
}
|
|
117
|
+
return conversations
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func updateUIViewController(_ uiViewController: CometChatConversations, context: Context) {}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 2. Tab Bar Pattern
|
|
127
|
+
|
|
128
|
+
For apps where chat is one of several main features.
|
|
129
|
+
|
|
130
|
+
### Tab Layout
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
134
|
+
│ │
|
|
135
|
+
│ Content Area │
|
|
136
|
+
│ │
|
|
137
|
+
│ │
|
|
138
|
+
│ │
|
|
139
|
+
├─────────────────────────────────────────────────────────────┤
|
|
140
|
+
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
|
141
|
+
│ │Home │ │Chats│ │Users│ │Groups│ │Calls│ │
|
|
142
|
+
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
|
|
143
|
+
└─────────────────────────────────────────────────────────────┘
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Implementation (UIKit)
|
|
147
|
+
|
|
148
|
+
```swift
|
|
149
|
+
import UIKit
|
|
150
|
+
import CometChatUIKitSwift
|
|
151
|
+
import CometChatSDK
|
|
152
|
+
|
|
153
|
+
class MainTabBarController: UITabBarController {
|
|
154
|
+
|
|
155
|
+
override func viewDidLoad() {
|
|
156
|
+
super.viewDidLoad()
|
|
157
|
+
setupTabs()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func setupTabs() {
|
|
161
|
+
// Home tab (your existing content)
|
|
162
|
+
let homeVC = HomeViewController()
|
|
163
|
+
homeVC.tabBarItem = UITabBarItem(
|
|
164
|
+
title: "Home",
|
|
165
|
+
image: UIImage(systemName: "house"),
|
|
166
|
+
selectedImage: UIImage(systemName: "house.fill")
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// Chats tab
|
|
170
|
+
let chatsVC = createChatsTab()
|
|
171
|
+
let chatsNav = UINavigationController(rootViewController: chatsVC)
|
|
172
|
+
chatsNav.tabBarItem = UITabBarItem(
|
|
173
|
+
title: "Chats",
|
|
174
|
+
image: UIImage(systemName: "message"),
|
|
175
|
+
selectedImage: UIImage(systemName: "message.fill")
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// Users tab
|
|
179
|
+
let usersVC = CometChatUsers()
|
|
180
|
+
usersVC.set(onItemClick: { [weak self] user, _ in
|
|
181
|
+
self?.openMessages(with: user)
|
|
182
|
+
})
|
|
183
|
+
let usersNav = UINavigationController(rootViewController: usersVC)
|
|
184
|
+
usersNav.tabBarItem = UITabBarItem(
|
|
185
|
+
title: "Users",
|
|
186
|
+
image: UIImage(systemName: "person.2"),
|
|
187
|
+
selectedImage: UIImage(systemName: "person.2.fill")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
// Groups tab
|
|
191
|
+
let groupsVC = CometChatGroups()
|
|
192
|
+
groupsVC.set(onItemClick: { [weak self] group, _ in
|
|
193
|
+
self?.openMessages(with: group)
|
|
194
|
+
})
|
|
195
|
+
let groupsNav = UINavigationController(rootViewController: groupsVC)
|
|
196
|
+
groupsNav.tabBarItem = UITabBarItem(
|
|
197
|
+
title: "Groups",
|
|
198
|
+
image: UIImage(systemName: "person.3"),
|
|
199
|
+
selectedImage: UIImage(systemName: "person.3.fill")
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
var tabs: [UIViewController] = [
|
|
203
|
+
UINavigationController(rootViewController: homeVC),
|
|
204
|
+
chatsNav,
|
|
205
|
+
usersNav,
|
|
206
|
+
groupsNav
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
// Calls tab - only available if CometChatCallsSDK is installed
|
|
210
|
+
#if canImport(CometChatCallsSDK)
|
|
211
|
+
let callsVC = CometChatCallLogs()
|
|
212
|
+
let callsNav = UINavigationController(rootViewController: callsVC)
|
|
213
|
+
callsNav.tabBarItem = UITabBarItem(
|
|
214
|
+
title: "Calls",
|
|
215
|
+
image: UIImage(systemName: "phone"),
|
|
216
|
+
selectedImage: UIImage(systemName: "phone.fill")
|
|
217
|
+
)
|
|
218
|
+
tabs.append(callsNav)
|
|
219
|
+
#endif
|
|
220
|
+
|
|
221
|
+
viewControllers = tabs
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private func createChatsTab() -> CometChatConversations {
|
|
225
|
+
let conversations = CometChatConversations()
|
|
226
|
+
conversations.set(onItemClick: { [weak self] conversation, _ in
|
|
227
|
+
if let user = conversation.conversationWith as? User {
|
|
228
|
+
self?.openMessages(with: user)
|
|
229
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
230
|
+
self?.openMessages(with: group)
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
return conversations
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private func openMessages(with user: User) {
|
|
237
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
238
|
+
messagesVC.set(user: user)
|
|
239
|
+
messagesVC.hidesBottomBarWhenPushed = true
|
|
240
|
+
|
|
241
|
+
if let navController = selectedViewController as? UINavigationController {
|
|
242
|
+
navController.pushViewController(messagesVC, animated: true)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private func openMessages(with group: Group) {
|
|
247
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
248
|
+
messagesVC.set(group: group)
|
|
249
|
+
messagesVC.hidesBottomBarWhenPushed = true
|
|
250
|
+
|
|
251
|
+
if let navController = selectedViewController as? UINavigationController {
|
|
252
|
+
navController.pushViewController(messagesVC, animated: true)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Implementation (SwiftUI)
|
|
259
|
+
|
|
260
|
+
```swift
|
|
261
|
+
struct MainTabView: View {
|
|
262
|
+
@State private var selectedTab = 0
|
|
263
|
+
|
|
264
|
+
var body: some View {
|
|
265
|
+
TabView(selection: $selectedTab) {
|
|
266
|
+
HomeView()
|
|
267
|
+
.tabItem {
|
|
268
|
+
Label("Home", systemImage: "house")
|
|
269
|
+
}
|
|
270
|
+
.tag(0)
|
|
271
|
+
|
|
272
|
+
ChatNavigationView()
|
|
273
|
+
.tabItem {
|
|
274
|
+
Label("Chats", systemImage: "message")
|
|
275
|
+
}
|
|
276
|
+
.tag(1)
|
|
277
|
+
|
|
278
|
+
UsersNavigationView()
|
|
279
|
+
.tabItem {
|
|
280
|
+
Label("Users", systemImage: "person.2")
|
|
281
|
+
}
|
|
282
|
+
.tag(2)
|
|
283
|
+
|
|
284
|
+
GroupsNavigationView()
|
|
285
|
+
.tabItem {
|
|
286
|
+
Label("Groups", systemImage: "person.3")
|
|
287
|
+
}
|
|
288
|
+
.tag(3)
|
|
289
|
+
|
|
290
|
+
CallsNavigationView()
|
|
291
|
+
.tabItem {
|
|
292
|
+
Label("Calls", systemImage: "phone")
|
|
293
|
+
}
|
|
294
|
+
.tag(4)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 3. Modal Presentation Pattern
|
|
303
|
+
|
|
304
|
+
For apps where chat is a secondary feature, presented modally.
|
|
305
|
+
|
|
306
|
+
### Modal Layout
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
310
|
+
│ Your App Content │
|
|
311
|
+
│ │
|
|
312
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
313
|
+
│ │ Product Details ││
|
|
314
|
+
│ │ ││
|
|
315
|
+
│ │ ┌─────────────────────────────────────────────────────┐││
|
|
316
|
+
│ │ │ [Chat with Seller] ◄─────────────────────────────│││
|
|
317
|
+
│ │ └─────────────────────────────────────────────────────┘││
|
|
318
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
319
|
+
└─────────────────────────────────────────────────────────────┘
|
|
320
|
+
│
|
|
321
|
+
▼ (modal presentation)
|
|
322
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
323
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
324
|
+
│ │ [X] Chat with John ││
|
|
325
|
+
│ ├─────────────────────────────────────────────────────────┤│
|
|
326
|
+
│ │ ││
|
|
327
|
+
│ │ Messages ││
|
|
328
|
+
│ │ ││
|
|
329
|
+
│ ├─────────────────────────────────────────────────────────┤│
|
|
330
|
+
│ │ Composer ││
|
|
331
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
332
|
+
└─────────────────────────────────────────────────────────────┘
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Implementation (UIKit)
|
|
336
|
+
|
|
337
|
+
```swift
|
|
338
|
+
class ProductDetailViewController: UIViewController {
|
|
339
|
+
|
|
340
|
+
var sellerUID: String?
|
|
341
|
+
|
|
342
|
+
@IBAction func chatWithSellerTapped(_ sender: UIButton) {
|
|
343
|
+
guard let sellerUID = sellerUID else { return }
|
|
344
|
+
|
|
345
|
+
// Fetch the seller user
|
|
346
|
+
CometChat.getUser(UID: sellerUID) { [weak self] user in
|
|
347
|
+
guard let user = user else { return }
|
|
348
|
+
|
|
349
|
+
DispatchQueue.main.async {
|
|
350
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
351
|
+
messagesVC.set(user: user)
|
|
352
|
+
|
|
353
|
+
let navController = UINavigationController(rootViewController: messagesVC)
|
|
354
|
+
navController.modalPresentationStyle = .pageSheet
|
|
355
|
+
|
|
356
|
+
// Add close button
|
|
357
|
+
messagesVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
358
|
+
barButtonSystemItem: .close,
|
|
359
|
+
target: self,
|
|
360
|
+
action: #selector(self?.dismissChat)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
self?.present(navController, animated: true)
|
|
364
|
+
}
|
|
365
|
+
} onError: { error in
|
|
366
|
+
print("Error fetching user: \(error?.errorDescription ?? "")")
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
@objc private func dismissChat() {
|
|
371
|
+
dismiss(animated: true)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Implementation (SwiftUI)
|
|
377
|
+
|
|
378
|
+
```swift
|
|
379
|
+
struct ProductDetailView: View {
|
|
380
|
+
let sellerUID: String
|
|
381
|
+
@State private var showChat = false
|
|
382
|
+
@State private var seller: User?
|
|
383
|
+
|
|
384
|
+
var body: some View {
|
|
385
|
+
VStack {
|
|
386
|
+
// Product details...
|
|
387
|
+
|
|
388
|
+
Button("Chat with Seller") {
|
|
389
|
+
fetchSellerAndShowChat()
|
|
390
|
+
}
|
|
391
|
+
.buttonStyle(.borderedProminent)
|
|
392
|
+
}
|
|
393
|
+
.sheet(isPresented: $showChat) {
|
|
394
|
+
if let seller = seller {
|
|
395
|
+
NavigationStack {
|
|
396
|
+
MessagesView(user: seller)
|
|
397
|
+
.toolbar {
|
|
398
|
+
ToolbarItem(placement: .navigationBarLeading) {
|
|
399
|
+
Button("Close") {
|
|
400
|
+
showChat = false
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private func fetchSellerAndShowChat() {
|
|
410
|
+
CometChat.getUser(UID: sellerUID) { user in
|
|
411
|
+
DispatchQueue.main.async {
|
|
412
|
+
self.seller = user
|
|
413
|
+
self.showChat = true
|
|
414
|
+
}
|
|
415
|
+
} onError: { error in
|
|
416
|
+
print("Error: \(error?.errorDescription ?? "")")
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 4. Floating Button Pattern
|
|
425
|
+
|
|
426
|
+
For support chat or quick access to messages.
|
|
427
|
+
|
|
428
|
+
### Floating Button Layout
|
|
429
|
+
|
|
430
|
+
```
|
|
431
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
432
|
+
│ Your App Content │
|
|
433
|
+
│ │
|
|
434
|
+
│ │
|
|
435
|
+
│ │
|
|
436
|
+
│ │
|
|
437
|
+
│ │
|
|
438
|
+
│ │
|
|
439
|
+
│ ┌─────┐ │
|
|
440
|
+
│ │ 💬 │ │
|
|
441
|
+
│ └─────┘ │
|
|
442
|
+
└─────────────────────────────────────────────────────────────┘
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Implementation (UIKit)
|
|
446
|
+
|
|
447
|
+
```swift
|
|
448
|
+
class FloatingChatButton: UIButton {
|
|
449
|
+
|
|
450
|
+
private var unreadCount: Int = 0 {
|
|
451
|
+
didSet {
|
|
452
|
+
updateBadge()
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private lazy var badgeLabel: UILabel = {
|
|
457
|
+
let label = UILabel()
|
|
458
|
+
label.backgroundColor = .systemRed
|
|
459
|
+
label.textColor = .white
|
|
460
|
+
label.font = .systemFont(ofSize: 12, weight: .bold)
|
|
461
|
+
label.textAlignment = .center
|
|
462
|
+
label.layer.cornerRadius = 10
|
|
463
|
+
label.clipsToBounds = true
|
|
464
|
+
label.isHidden = true
|
|
465
|
+
return label
|
|
466
|
+
}()
|
|
467
|
+
|
|
468
|
+
override init(frame: CGRect) {
|
|
469
|
+
super.init(frame: frame)
|
|
470
|
+
setupButton()
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
required init?(coder: NSCoder) {
|
|
474
|
+
super.init(coder: coder)
|
|
475
|
+
setupButton()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private func setupButton() {
|
|
479
|
+
backgroundColor = .systemBlue
|
|
480
|
+
layer.cornerRadius = 28
|
|
481
|
+
layer.shadowColor = UIColor.black.cgColor
|
|
482
|
+
layer.shadowOffset = CGSize(width: 0, height: 4)
|
|
483
|
+
layer.shadowRadius = 8
|
|
484
|
+
layer.shadowOpacity = 0.3
|
|
485
|
+
|
|
486
|
+
setImage(UIImage(systemName: "message.fill"), for: .normal)
|
|
487
|
+
tintColor = .white
|
|
488
|
+
|
|
489
|
+
addSubview(badgeLabel)
|
|
490
|
+
badgeLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
491
|
+
NSLayoutConstraint.activate([
|
|
492
|
+
badgeLabel.topAnchor.constraint(equalTo: topAnchor, constant: -5),
|
|
493
|
+
badgeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 5),
|
|
494
|
+
badgeLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
|
|
495
|
+
badgeLabel.heightAnchor.constraint(equalToConstant: 20)
|
|
496
|
+
])
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private func updateBadge() {
|
|
500
|
+
badgeLabel.isHidden = unreadCount == 0
|
|
501
|
+
badgeLabel.text = unreadCount > 99 ? "99+" : "\(unreadCount)"
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
func setUnreadCount(_ count: Int) {
|
|
505
|
+
unreadCount = count
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Usage in a view controller
|
|
510
|
+
class MainViewController: UIViewController {
|
|
511
|
+
|
|
512
|
+
private lazy var chatButton: FloatingChatButton = {
|
|
513
|
+
let button = FloatingChatButton()
|
|
514
|
+
button.addTarget(self, action: #selector(openChat), for: .touchUpInside)
|
|
515
|
+
return button
|
|
516
|
+
}()
|
|
517
|
+
|
|
518
|
+
override func viewDidLoad() {
|
|
519
|
+
super.viewDidLoad()
|
|
520
|
+
setupFloatingButton()
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private func setupFloatingButton() {
|
|
524
|
+
view.addSubview(chatButton)
|
|
525
|
+
chatButton.translatesAutoresizingMaskIntoConstraints = false
|
|
526
|
+
NSLayoutConstraint.activate([
|
|
527
|
+
chatButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
|
|
528
|
+
chatButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
|
|
529
|
+
chatButton.widthAnchor.constraint(equalToConstant: 56),
|
|
530
|
+
chatButton.heightAnchor.constraint(equalToConstant: 56)
|
|
531
|
+
])
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
@objc private func openChat() {
|
|
535
|
+
let conversations = CometChatConversations()
|
|
536
|
+
conversations.set(onItemClick: { [weak self] conversation, _ in
|
|
537
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
538
|
+
if let user = conversation.conversationWith as? User {
|
|
539
|
+
messagesVC.set(user: user)
|
|
540
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
541
|
+
messagesVC.set(group: group)
|
|
542
|
+
}
|
|
543
|
+
conversations.navigationController?.pushViewController(messagesVC, animated: true)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
let navController = UINavigationController(rootViewController: conversations)
|
|
547
|
+
navController.modalPresentationStyle = .pageSheet
|
|
548
|
+
|
|
549
|
+
conversations.navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
550
|
+
barButtonSystemItem: .close,
|
|
551
|
+
target: self,
|
|
552
|
+
action: #selector(dismissChat)
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
present(navController, animated: true)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
@objc private func dismissChat() {
|
|
559
|
+
dismiss(animated: true)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 5. Split View Pattern (iPad)
|
|
567
|
+
|
|
568
|
+
For iPad apps with master-detail layout.
|
|
569
|
+
|
|
570
|
+
### Split View Layout
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
574
|
+
│ ┌─────────────────────────┐ ┌─────────────────────────────────────────────┐│
|
|
575
|
+
│ │ Conversations │ │ Messages ││
|
|
576
|
+
│ │ ┌─────────────────────┐│ │ ┌─────────────────────────────────────────┐││
|
|
577
|
+
│ │ │ John Doe ││ │ │ Header │││
|
|
578
|
+
│ │ │ Jane Smith ◄──────┼┼──┼──┤─────────────────────────────────────────│││
|
|
579
|
+
│ │ │ Team Chat ││ │ │ Message List │││
|
|
580
|
+
│ │ │ ││ │ │ │││
|
|
581
|
+
│ │ │ ││ │ │ │││
|
|
582
|
+
│ │ │ ││ │ ├─────────────────────────────────────────┤││
|
|
583
|
+
│ │ │ ││ │ │ Composer │││
|
|
584
|
+
│ │ └─────────────────────┘│ │ └─────────────────────────────────────────┘││
|
|
585
|
+
│ └─────────────────────────┘ └─────────────────────────────────────────────┘│
|
|
586
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Implementation (UIKit)
|
|
590
|
+
|
|
591
|
+
```swift
|
|
592
|
+
class ChatSplitViewController: UISplitViewController {
|
|
593
|
+
|
|
594
|
+
override func viewDidLoad() {
|
|
595
|
+
super.viewDidLoad()
|
|
596
|
+
|
|
597
|
+
preferredDisplayMode = .oneBesideSecondary
|
|
598
|
+
preferredSplitBehavior = .tile
|
|
599
|
+
|
|
600
|
+
// Primary: Conversations
|
|
601
|
+
let conversations = CometChatConversations()
|
|
602
|
+
conversations.set(onItemClick: { [weak self] conversation, _ in
|
|
603
|
+
self?.showMessages(for: conversation)
|
|
604
|
+
})
|
|
605
|
+
let primaryNav = UINavigationController(rootViewController: conversations)
|
|
606
|
+
|
|
607
|
+
// Secondary: Empty state or messages
|
|
608
|
+
let emptyVC = EmptyStateViewController()
|
|
609
|
+
let secondaryNav = UINavigationController(rootViewController: emptyVC)
|
|
610
|
+
|
|
611
|
+
viewControllers = [primaryNav, secondaryNav]
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private func showMessages(for conversation: Conversation) {
|
|
615
|
+
let messagesVC = MessagesVC() // your own VC composing CometChatMessageHeader + List + Composer; see cometchat-ios-components § 12
|
|
616
|
+
|
|
617
|
+
if let user = conversation.conversationWith as? User {
|
|
618
|
+
messagesVC.set(user: user)
|
|
619
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
620
|
+
messagesVC.set(group: group)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
let secondaryNav = UINavigationController(rootViewController: messagesVC)
|
|
624
|
+
showDetailViewController(secondaryNav, sender: self)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
class EmptyStateViewController: UIViewController {
|
|
629
|
+
override func viewDidLoad() {
|
|
630
|
+
super.viewDidLoad()
|
|
631
|
+
view.backgroundColor = .systemBackground
|
|
632
|
+
|
|
633
|
+
let label = UILabel()
|
|
634
|
+
label.text = "Select a conversation"
|
|
635
|
+
label.textColor = .secondaryLabel
|
|
636
|
+
label.textAlignment = .center
|
|
637
|
+
|
|
638
|
+
view.addSubview(label)
|
|
639
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
640
|
+
NSLayoutConstraint.activate([
|
|
641
|
+
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
642
|
+
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
|
643
|
+
])
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
## 6. Embedded View Pattern
|
|
651
|
+
|
|
652
|
+
For embedding chat in a portion of the screen.
|
|
653
|
+
|
|
654
|
+
### Embedded Layout
|
|
655
|
+
|
|
656
|
+
```
|
|
657
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
658
|
+
│ Your App Header │
|
|
659
|
+
├─────────────────────────────────────────────────────────────┤
|
|
660
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
661
|
+
│ │ Your Content ││
|
|
662
|
+
│ │ ││
|
|
663
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
664
|
+
├─────────────────────────────────────────────────────────────┤
|
|
665
|
+
│ ┌─────────────────────────────────────────────────────────┐│
|
|
666
|
+
│ │ Embedded Chat (CometChatMessageList + Composer) ││
|
|
667
|
+
│ │ ││
|
|
668
|
+
│ └─────────────────────────────────────────────────────────┘│
|
|
669
|
+
└─────────────────────────────────────────────────────────────┘
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Implementation (UIKit)
|
|
673
|
+
|
|
674
|
+
```swift
|
|
675
|
+
class EmbeddedChatViewController: UIViewController {
|
|
676
|
+
|
|
677
|
+
private var supportUser: User?
|
|
678
|
+
|
|
679
|
+
private lazy var chatContainer: UIView = {
|
|
680
|
+
let view = UIView()
|
|
681
|
+
view.backgroundColor = .systemBackground
|
|
682
|
+
view.layer.cornerRadius = 12
|
|
683
|
+
view.clipsToBounds = true
|
|
684
|
+
return view
|
|
685
|
+
}()
|
|
686
|
+
|
|
687
|
+
private lazy var messageList = CometChatMessageList()
|
|
688
|
+
private lazy var messageComposer = CometChatMessageComposer()
|
|
689
|
+
|
|
690
|
+
override func viewDidLoad() {
|
|
691
|
+
super.viewDidLoad()
|
|
692
|
+
setupUI()
|
|
693
|
+
fetchSupportUser()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
private func setupUI() {
|
|
697
|
+
view.addSubview(chatContainer)
|
|
698
|
+
chatContainer.translatesAutoresizingMaskIntoConstraints = false
|
|
699
|
+
|
|
700
|
+
chatContainer.addSubview(messageList)
|
|
701
|
+
chatContainer.addSubview(messageComposer)
|
|
702
|
+
|
|
703
|
+
messageList.translatesAutoresizingMaskIntoConstraints = false
|
|
704
|
+
messageComposer.translatesAutoresizingMaskIntoConstraints = false
|
|
705
|
+
|
|
706
|
+
NSLayoutConstraint.activate([
|
|
707
|
+
// Chat container takes bottom half of screen
|
|
708
|
+
chatContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
|
709
|
+
chatContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
|
710
|
+
chatContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
|
|
711
|
+
chatContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.4),
|
|
712
|
+
|
|
713
|
+
// Message list
|
|
714
|
+
messageList.topAnchor.constraint(equalTo: chatContainer.topAnchor),
|
|
715
|
+
messageList.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
|
|
716
|
+
messageList.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
|
|
717
|
+
messageList.bottomAnchor.constraint(equalTo: messageComposer.topAnchor),
|
|
718
|
+
|
|
719
|
+
// Composer
|
|
720
|
+
messageComposer.leadingAnchor.constraint(equalTo: chatContainer.leadingAnchor),
|
|
721
|
+
messageComposer.trailingAnchor.constraint(equalTo: chatContainer.trailingAnchor),
|
|
722
|
+
messageComposer.bottomAnchor.constraint(equalTo: chatContainer.bottomAnchor)
|
|
723
|
+
])
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
private func fetchSupportUser() {
|
|
727
|
+
CometChat.getUser(UID: "support-agent") { [weak self] user in
|
|
728
|
+
guard let user = user else { return }
|
|
729
|
+
DispatchQueue.main.async {
|
|
730
|
+
self?.supportUser = user
|
|
731
|
+
self?.messageList.set(user: user)
|
|
732
|
+
self?.messageComposer.set(user: user)
|
|
733
|
+
}
|
|
734
|
+
} onError: { error in
|
|
735
|
+
print("Error: \(error?.errorDescription ?? "")")
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## Best Practices
|
|
744
|
+
|
|
745
|
+
1. **Always wrap in UINavigationController** when presenting CometChat view controllers
|
|
746
|
+
2. **Hide tab bar when pushing messages** using `hidesBottomBarWhenPushed = true`
|
|
747
|
+
3. **Handle keyboard properly** — CometChat components handle this automatically
|
|
748
|
+
4. **Support both orientations** — components adapt to orientation changes
|
|
749
|
+
5. **Test on iPad** — use split view for better iPad experience
|
|
750
|
+
6. **Handle deep links** — navigate to specific conversations from push notifications
|