@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,825 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cometchat-ios-customization
|
|
3
|
+
description: "Customize CometChat iOS UI Kit beyond theming — custom views, message templates, text formatters, and event handling."
|
|
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 customization templates formatters events"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
This skill teaches advanced customization of CometChat iOS UI Kit — custom views, message templates, text formatters, event listeners, and request builder filters.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Custom Views in CometChatConversations
|
|
20
|
+
|
|
21
|
+
### Custom List Item View
|
|
22
|
+
|
|
23
|
+
Replace the entire list item with a custom view:
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
let conversations = CometChatConversations()
|
|
27
|
+
|
|
28
|
+
conversations.set(listItemView: { (conversation: Conversation) -> UIView in
|
|
29
|
+
let customView = UIView()
|
|
30
|
+
customView.backgroundColor = .secondarySystemBackground
|
|
31
|
+
|
|
32
|
+
let nameLabel = UILabel()
|
|
33
|
+
nameLabel.font = .boldSystemFont(ofSize: 16)
|
|
34
|
+
|
|
35
|
+
if let user = conversation.conversationWith as? User {
|
|
36
|
+
nameLabel.text = user.name
|
|
37
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
38
|
+
nameLabel.text = group.name
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
customView.addSubview(nameLabel)
|
|
42
|
+
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
43
|
+
NSLayoutConstraint.activate([
|
|
44
|
+
nameLabel.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 16),
|
|
45
|
+
nameLabel.centerYAnchor.constraint(equalTo: customView.centerYAnchor)
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
return customView
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Custom Leading View (Avatar Area)
|
|
53
|
+
|
|
54
|
+
```swift
|
|
55
|
+
conversations.set(leadingView: { (conversation: Conversation) -> UIView in
|
|
56
|
+
let avatarView = UIView()
|
|
57
|
+
avatarView.backgroundColor = .systemBlue
|
|
58
|
+
avatarView.layer.cornerRadius = 24
|
|
59
|
+
|
|
60
|
+
let initialsLabel = UILabel()
|
|
61
|
+
initialsLabel.textColor = .white
|
|
62
|
+
initialsLabel.font = .boldSystemFont(ofSize: 14)
|
|
63
|
+
initialsLabel.textAlignment = .center
|
|
64
|
+
|
|
65
|
+
if let user = conversation.conversationWith as? User {
|
|
66
|
+
initialsLabel.text = String(user.name?.prefix(2).uppercased() ?? "")
|
|
67
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
68
|
+
initialsLabel.text = String(group.name?.prefix(2).uppercased() ?? "")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
avatarView.addSubview(initialsLabel)
|
|
72
|
+
initialsLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
73
|
+
NSLayoutConstraint.activate([
|
|
74
|
+
initialsLabel.centerXAnchor.constraint(equalTo: avatarView.centerXAnchor),
|
|
75
|
+
initialsLabel.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor),
|
|
76
|
+
avatarView.widthAnchor.constraint(equalToConstant: 48),
|
|
77
|
+
avatarView.heightAnchor.constraint(equalToConstant: 48)
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
return avatarView
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Custom Title View
|
|
85
|
+
|
|
86
|
+
```swift
|
|
87
|
+
conversations.set(titleView: { (conversation: Conversation) -> UIView in
|
|
88
|
+
let stackView = UIStackView()
|
|
89
|
+
stackView.axis = .horizontal
|
|
90
|
+
stackView.spacing = 8
|
|
91
|
+
|
|
92
|
+
let nameLabel = UILabel()
|
|
93
|
+
nameLabel.font = .boldSystemFont(ofSize: 16)
|
|
94
|
+
|
|
95
|
+
let verifiedBadge = UIImageView(image: UIImage(systemName: "checkmark.seal.fill"))
|
|
96
|
+
verifiedBadge.tintColor = .systemBlue
|
|
97
|
+
verifiedBadge.isHidden = true
|
|
98
|
+
|
|
99
|
+
if let user = conversation.conversationWith as? User {
|
|
100
|
+
nameLabel.text = user.name
|
|
101
|
+
// Show badge for verified users (custom metadata)
|
|
102
|
+
if let metadata = user.metadata, metadata["verified"] as? Bool == true {
|
|
103
|
+
verifiedBadge.isHidden = false
|
|
104
|
+
}
|
|
105
|
+
} else if let group = conversation.conversationWith as? Group {
|
|
106
|
+
nameLabel.text = group.name
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
stackView.addArrangedSubview(nameLabel)
|
|
110
|
+
stackView.addArrangedSubview(verifiedBadge)
|
|
111
|
+
|
|
112
|
+
return stackView
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Custom Subtitle View
|
|
117
|
+
|
|
118
|
+
```swift
|
|
119
|
+
conversations.set(subtitleView: { (conversation: Conversation) -> UIView in
|
|
120
|
+
let label = UILabel()
|
|
121
|
+
label.font = .systemFont(ofSize: 14)
|
|
122
|
+
label.textColor = .secondaryLabel
|
|
123
|
+
|
|
124
|
+
if let lastMessage = conversation.lastMessage as? TextMessage {
|
|
125
|
+
label.text = lastMessage.text
|
|
126
|
+
} else if let lastMessage = conversation.lastMessage as? MediaMessage {
|
|
127
|
+
switch lastMessage.messageType {
|
|
128
|
+
case .image:
|
|
129
|
+
label.text = "📷 Photo"
|
|
130
|
+
case .video:
|
|
131
|
+
label.text = "🎥 Video"
|
|
132
|
+
case .audio:
|
|
133
|
+
label.text = "🎵 Audio"
|
|
134
|
+
case .file:
|
|
135
|
+
label.text = "📎 File"
|
|
136
|
+
default:
|
|
137
|
+
label.text = "Attachment"
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
label.text = "No messages yet"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return label
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Custom Tail View
|
|
148
|
+
|
|
149
|
+
```swift
|
|
150
|
+
conversations.set(trailView: { (conversation: Conversation) -> UIView in
|
|
151
|
+
let stackView = UIStackView()
|
|
152
|
+
stackView.axis = .vertical
|
|
153
|
+
stackView.alignment = .trailing
|
|
154
|
+
stackView.spacing = 4
|
|
155
|
+
|
|
156
|
+
// Time label
|
|
157
|
+
let timeLabel = UILabel()
|
|
158
|
+
timeLabel.font = .systemFont(ofSize: 12)
|
|
159
|
+
timeLabel.textColor = .tertiaryLabel
|
|
160
|
+
|
|
161
|
+
if let timestamp = conversation.lastMessage?.sentAt {
|
|
162
|
+
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
|
163
|
+
let formatter = DateFormatter()
|
|
164
|
+
formatter.dateFormat = "h:mm a"
|
|
165
|
+
timeLabel.text = formatter.string(from: date)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Unread badge
|
|
169
|
+
let badge = UILabel()
|
|
170
|
+
badge.font = .boldSystemFont(ofSize: 12)
|
|
171
|
+
badge.textColor = .white
|
|
172
|
+
badge.backgroundColor = .systemBlue
|
|
173
|
+
badge.textAlignment = .center
|
|
174
|
+
badge.layer.cornerRadius = 10
|
|
175
|
+
badge.clipsToBounds = true
|
|
176
|
+
|
|
177
|
+
let unreadCount = conversation.unreadMessageCount
|
|
178
|
+
badge.isHidden = unreadCount == 0
|
|
179
|
+
badge.text = unreadCount > 99 ? "99+" : "\(unreadCount)"
|
|
180
|
+
|
|
181
|
+
stackView.addArrangedSubview(timeLabel)
|
|
182
|
+
stackView.addArrangedSubview(badge)
|
|
183
|
+
|
|
184
|
+
NSLayoutConstraint.activate([
|
|
185
|
+
badge.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
|
|
186
|
+
badge.heightAnchor.constraint(equalToConstant: 20)
|
|
187
|
+
])
|
|
188
|
+
|
|
189
|
+
return stackView
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 2. Custom Swipe Actions
|
|
196
|
+
|
|
197
|
+
### Custom Options
|
|
198
|
+
|
|
199
|
+
```swift
|
|
200
|
+
conversations.set(options: { (conversation: Conversation?) -> [CometChatConversationOption] in
|
|
201
|
+
guard let conversation = conversation else { return [] }
|
|
202
|
+
var options: [CometChatConversationOption] = []
|
|
203
|
+
|
|
204
|
+
// Pin conversation
|
|
205
|
+
let pinOption = CometChatConversationOption(
|
|
206
|
+
id: "pin",
|
|
207
|
+
title: "Pin",
|
|
208
|
+
icon: UIImage(systemName: "pin.fill"),
|
|
209
|
+
backgroundColor: .systemYellow,
|
|
210
|
+
iconTint: .white
|
|
211
|
+
)
|
|
212
|
+
pinOption.onClick = { _, _, _, _ in
|
|
213
|
+
// Handle pin action
|
|
214
|
+
print("Pin conversation: \(conversation.conversationId ?? "")")
|
|
215
|
+
}
|
|
216
|
+
options.append(pinOption)
|
|
217
|
+
|
|
218
|
+
// Mute conversation
|
|
219
|
+
let muteOption = CometChatConversationOption(
|
|
220
|
+
id: "mute",
|
|
221
|
+
title: "Mute",
|
|
222
|
+
icon: UIImage(systemName: "bell.slash.fill"),
|
|
223
|
+
backgroundColor: .systemGray,
|
|
224
|
+
iconTint: .white
|
|
225
|
+
)
|
|
226
|
+
muteOption.onClick = { _, _, _, _ in
|
|
227
|
+
// Handle mute action
|
|
228
|
+
print("Mute conversation: \(conversation.conversationId ?? "")")
|
|
229
|
+
}
|
|
230
|
+
options.append(muteOption)
|
|
231
|
+
|
|
232
|
+
// Delete conversation
|
|
233
|
+
let deleteOption = CometChatConversationOption(
|
|
234
|
+
id: "delete",
|
|
235
|
+
title: "Delete",
|
|
236
|
+
icon: UIImage(systemName: "trash.fill"),
|
|
237
|
+
backgroundColor: .systemRed,
|
|
238
|
+
iconTint: .white
|
|
239
|
+
)
|
|
240
|
+
deleteOption.onClick = { _, _, _, _ in
|
|
241
|
+
// Handle delete action
|
|
242
|
+
print("Delete conversation: \(conversation.conversationId ?? "")")
|
|
243
|
+
}
|
|
244
|
+
options.append(deleteOption)
|
|
245
|
+
|
|
246
|
+
return options
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Add Options (Keep Default + Add Custom)
|
|
251
|
+
|
|
252
|
+
```swift
|
|
253
|
+
conversations.add(options: { (conversation: Conversation?) -> [CometChatConversationOption] in
|
|
254
|
+
let archiveOption = CometChatConversationOption(
|
|
255
|
+
id: "archive",
|
|
256
|
+
title: "Archive",
|
|
257
|
+
icon: UIImage(systemName: "archivebox.fill"),
|
|
258
|
+
backgroundColor: .systemPurple,
|
|
259
|
+
iconTint: .white
|
|
260
|
+
)
|
|
261
|
+
archiveOption.onClick = { _, _, _, _ in
|
|
262
|
+
print("Archive conversation")
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return [archiveOption]
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 3. Custom Message Templates
|
|
272
|
+
|
|
273
|
+
### Creating a Custom Message Template
|
|
274
|
+
|
|
275
|
+
```swift
|
|
276
|
+
let messageList = CometChatMessageList()
|
|
277
|
+
|
|
278
|
+
// Get default templates
|
|
279
|
+
var templates = CometChatUIKit.getDataSource().getAllMessageTemplates()
|
|
280
|
+
|
|
281
|
+
// Create custom template for a specific message type
|
|
282
|
+
let customTemplate = CometChatMessageTemplate(
|
|
283
|
+
type: "custom_poll",
|
|
284
|
+
category: "custom"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// Custom content view
|
|
288
|
+
customTemplate.contentView = { message, alignment, controller in
|
|
289
|
+
guard let customMessage = message as? CustomMessage,
|
|
290
|
+
let data = customMessage.customData else {
|
|
291
|
+
return UIView()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let pollView = PollBubbleView()
|
|
295
|
+
pollView.configure(with: data)
|
|
296
|
+
return pollView
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Custom header view (above the bubble)
|
|
300
|
+
customTemplate.headerView = { message, alignment, controller in
|
|
301
|
+
let label = UILabel()
|
|
302
|
+
label.text = "📊 Poll"
|
|
303
|
+
label.font = .boldSystemFont(ofSize: 12)
|
|
304
|
+
label.textColor = .secondaryLabel
|
|
305
|
+
return label
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Custom footer view (below the bubble)
|
|
309
|
+
customTemplate.footerView = { message, alignment, controller in
|
|
310
|
+
let label = UILabel()
|
|
311
|
+
label.text = "Tap to vote"
|
|
312
|
+
label.font = .systemFont(ofSize: 11)
|
|
313
|
+
label.textColor = .tertiaryLabel
|
|
314
|
+
return label
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Custom bubble view (wraps content)
|
|
318
|
+
customTemplate.bubbleView = { message, alignment, controller in
|
|
319
|
+
let bubble = UIView()
|
|
320
|
+
bubble.backgroundColor = alignment == .right
|
|
321
|
+
? CometChatTheme.primaryColor.withAlphaComponent(0.1)
|
|
322
|
+
: UIColor.secondarySystemBackground
|
|
323
|
+
bubble.layer.cornerRadius = 12
|
|
324
|
+
return bubble
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Custom options (long press menu)
|
|
328
|
+
customTemplate.options = { message, group, controller in
|
|
329
|
+
let viewResultsOption = CometChatMessageOption(
|
|
330
|
+
id: "view_results",
|
|
331
|
+
title: "View Results",
|
|
332
|
+
icon: UIImage(systemName: "chart.bar.fill")
|
|
333
|
+
)
|
|
334
|
+
viewResultsOption.onClick = { _, _, _, _ in
|
|
335
|
+
// Show poll results
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return [viewResultsOption]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
templates.append(customTemplate)
|
|
342
|
+
messageList.set(templates: templates)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Custom Poll Bubble View Example
|
|
346
|
+
|
|
347
|
+
```swift
|
|
348
|
+
class PollBubbleView: UIView {
|
|
349
|
+
|
|
350
|
+
private let questionLabel = UILabel()
|
|
351
|
+
private let optionsStack = UIStackView()
|
|
352
|
+
|
|
353
|
+
override init(frame: CGRect) {
|
|
354
|
+
super.init(frame: frame)
|
|
355
|
+
setupUI()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
required init?(coder: NSCoder) {
|
|
359
|
+
super.init(coder: coder)
|
|
360
|
+
setupUI()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private func setupUI() {
|
|
364
|
+
questionLabel.font = .boldSystemFont(ofSize: 16)
|
|
365
|
+
questionLabel.numberOfLines = 0
|
|
366
|
+
|
|
367
|
+
optionsStack.axis = .vertical
|
|
368
|
+
optionsStack.spacing = 8
|
|
369
|
+
|
|
370
|
+
addSubview(questionLabel)
|
|
371
|
+
addSubview(optionsStack)
|
|
372
|
+
|
|
373
|
+
questionLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
374
|
+
optionsStack.translatesAutoresizingMaskIntoConstraints = false
|
|
375
|
+
|
|
376
|
+
NSLayoutConstraint.activate([
|
|
377
|
+
questionLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
|
|
378
|
+
questionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
|
|
379
|
+
questionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
|
|
380
|
+
|
|
381
|
+
optionsStack.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 12),
|
|
382
|
+
optionsStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
|
|
383
|
+
optionsStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
|
|
384
|
+
optionsStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12)
|
|
385
|
+
])
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
func configure(with data: [String: Any]) {
|
|
389
|
+
questionLabel.text = data["question"] as? String
|
|
390
|
+
|
|
391
|
+
optionsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
392
|
+
|
|
393
|
+
if let options = data["options"] as? [String] {
|
|
394
|
+
for option in options {
|
|
395
|
+
let button = UIButton(type: .system)
|
|
396
|
+
button.setTitle(option, for: .normal)
|
|
397
|
+
button.backgroundColor = .systemGray5
|
|
398
|
+
button.layer.cornerRadius = 8
|
|
399
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12)
|
|
400
|
+
optionsStack.addArrangedSubview(button)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## 4. Text Formatters
|
|
410
|
+
|
|
411
|
+
### Creating a Custom Text Formatter
|
|
412
|
+
|
|
413
|
+
```swift
|
|
414
|
+
class HashtagFormatter: CometChatTextFormatter {
|
|
415
|
+
|
|
416
|
+
override func getRegex() -> String {
|
|
417
|
+
return "#[a-zA-Z0-9_]+"
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
override func prepareMessageString(
|
|
421
|
+
baseMessage: BaseMessage,
|
|
422
|
+
regexString: String,
|
|
423
|
+
alignment: MessageBubbleAlignment,
|
|
424
|
+
formattingType: FormattingType
|
|
425
|
+
) -> NSAttributedString {
|
|
426
|
+
|
|
427
|
+
guard let textMessage = baseMessage as? TextMessage,
|
|
428
|
+
let text = textMessage.text else {
|
|
429
|
+
return NSAttributedString()
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let attributedString = NSMutableAttributedString(string: text)
|
|
433
|
+
|
|
434
|
+
// Default text attributes
|
|
435
|
+
let defaultAttributes: [NSAttributedString.Key: Any] = [
|
|
436
|
+
.font: CometChatTypography.Body.regular,
|
|
437
|
+
.foregroundColor: alignment == .right ? UIColor.white : UIColor.label
|
|
438
|
+
]
|
|
439
|
+
attributedString.addAttributes(defaultAttributes, range: NSRange(location: 0, length: text.count))
|
|
440
|
+
|
|
441
|
+
// Highlight hashtags
|
|
442
|
+
let regex = try? NSRegularExpression(pattern: regexString, options: [])
|
|
443
|
+
let matches = regex?.matches(in: text, options: [], range: NSRange(location: 0, length: text.count)) ?? []
|
|
444
|
+
|
|
445
|
+
for match in matches {
|
|
446
|
+
let hashtagAttributes: [NSAttributedString.Key: Any] = [
|
|
447
|
+
.foregroundColor: UIColor.systemBlue,
|
|
448
|
+
.font: CometChatTypography.Body.medium
|
|
449
|
+
]
|
|
450
|
+
attributedString.addAttributes(hashtagAttributes, range: match.range)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return attributedString
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
override func onTextTapped(
|
|
457
|
+
baseMessage: BaseMessage,
|
|
458
|
+
tappedText: String,
|
|
459
|
+
controller: UIViewController?
|
|
460
|
+
) {
|
|
461
|
+
// Handle hashtag tap
|
|
462
|
+
print("Hashtag tapped: \(tappedText)")
|
|
463
|
+
// Navigate to hashtag search, etc.
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Usage
|
|
468
|
+
let messageList = CometChatMessageList()
|
|
469
|
+
messageList.textFormatter = [
|
|
470
|
+
CometChatMentionsFormatter(), // Built-in mentions
|
|
471
|
+
HashtagFormatter() // Custom hashtags
|
|
472
|
+
]
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### URL Formatter with Custom Preview
|
|
476
|
+
|
|
477
|
+
```swift
|
|
478
|
+
class CustomURLFormatter: CometChatTextFormatter {
|
|
479
|
+
|
|
480
|
+
override func getRegex() -> String {
|
|
481
|
+
return "https?://[^\\s]+"
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
override func prepareMessageString(
|
|
485
|
+
baseMessage: BaseMessage,
|
|
486
|
+
regexString: String,
|
|
487
|
+
alignment: MessageBubbleAlignment,
|
|
488
|
+
formattingType: FormattingType
|
|
489
|
+
) -> NSAttributedString {
|
|
490
|
+
|
|
491
|
+
guard let textMessage = baseMessage as? TextMessage,
|
|
492
|
+
let text = textMessage.text else {
|
|
493
|
+
return NSAttributedString()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let attributedString = NSMutableAttributedString(string: text)
|
|
497
|
+
|
|
498
|
+
let regex = try? NSRegularExpression(pattern: regexString, options: [])
|
|
499
|
+
let matches = regex?.matches(in: text, options: [], range: NSRange(location: 0, length: text.count)) ?? []
|
|
500
|
+
|
|
501
|
+
for match in matches {
|
|
502
|
+
let urlAttributes: [NSAttributedString.Key: Any] = [
|
|
503
|
+
.foregroundColor: UIColor.systemBlue,
|
|
504
|
+
.underlineStyle: NSUnderlineStyle.single.rawValue
|
|
505
|
+
]
|
|
506
|
+
attributedString.addAttributes(urlAttributes, range: match.range)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return attributedString
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
override func onTextTapped(
|
|
513
|
+
baseMessage: BaseMessage,
|
|
514
|
+
tappedText: String,
|
|
515
|
+
controller: UIViewController?
|
|
516
|
+
) {
|
|
517
|
+
if let url = URL(string: tappedText) {
|
|
518
|
+
UIApplication.shared.open(url)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## 5. Event Listeners
|
|
527
|
+
|
|
528
|
+
### Message Events
|
|
529
|
+
|
|
530
|
+
```swift
|
|
531
|
+
import CometChatSDK
|
|
532
|
+
|
|
533
|
+
class MessageEventListener: CometChatMessageEventListener {
|
|
534
|
+
|
|
535
|
+
func ccMessageSent(message: BaseMessage, status: MessageStatus) {
|
|
536
|
+
switch status {
|
|
537
|
+
case .inProgress:
|
|
538
|
+
print("Message sending...")
|
|
539
|
+
case .success:
|
|
540
|
+
print("Message sent successfully")
|
|
541
|
+
case .error:
|
|
542
|
+
print("Message failed to send")
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
func ccMessageEdited(message: BaseMessage, status: MessageStatus) {
|
|
547
|
+
print("Message edited: \(message.id)")
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
func ccMessageDeleted(message: BaseMessage) {
|
|
551
|
+
print("Message deleted: \(message.id)")
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
func ccMessageRead(message: BaseMessage) {
|
|
555
|
+
print("Message read: \(message.id)")
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
func ccLiveReaction(reaction: TransientMessage) {
|
|
559
|
+
print("Live reaction received")
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Register listener
|
|
564
|
+
let listener = MessageEventListener()
|
|
565
|
+
CometChatMessageEvents.addListener("message-listener", listener)
|
|
566
|
+
|
|
567
|
+
// Remove listener when done
|
|
568
|
+
CometChatMessageEvents.removeListener("message-listener")
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### User Events
|
|
572
|
+
|
|
573
|
+
```swift
|
|
574
|
+
class UserEventListener: CometChatUserEventListener {
|
|
575
|
+
|
|
576
|
+
func ccUserBlocked(user: User) {
|
|
577
|
+
print("User blocked: \(user.name ?? "")")
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
func ccUserUnblocked(user: User) {
|
|
581
|
+
print("User unblocked: \(user.name ?? "")")
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Register
|
|
586
|
+
CometChatUserEvents.addListener("user-listener", UserEventListener())
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Group Events
|
|
590
|
+
|
|
591
|
+
```swift
|
|
592
|
+
class GroupEventListener: CometChatGroupEventListener {
|
|
593
|
+
|
|
594
|
+
func ccGroupCreated(group: Group) {
|
|
595
|
+
print("Group created: \(group.name ?? "")")
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
func ccGroupDeleted(group: Group) {
|
|
599
|
+
print("Group deleted: \(group.name ?? "")")
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
func ccGroupMemberAdded(messages: [ActionMessage], usersAdded: [User], groupAddedIn: Group, addedBy: User) {
|
|
603
|
+
print("Members added to group")
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
func ccGroupMemberKicked(message: ActionMessage, kickedUser: User, kickedBy: User, kickedFrom: Group) {
|
|
607
|
+
print("Member kicked from group")
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
func ccGroupMemberBanned(message: ActionMessage, bannedUser: User, bannedBy: User, bannedFrom: Group) {
|
|
611
|
+
print("Member banned from group")
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
func ccOwnershipChanged(group: Group, newOwner: GroupMember) {
|
|
615
|
+
print("Group ownership changed")
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Register
|
|
620
|
+
CometChatGroupEvents.addListener("group-listener", GroupEventListener())
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Call Events
|
|
624
|
+
|
|
625
|
+
```swift
|
|
626
|
+
class CallEventListener: CometChatCallEventListener {
|
|
627
|
+
|
|
628
|
+
func ccCallAccepted(call: Call) {
|
|
629
|
+
print("Call accepted")
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
func ccCallRejected(call: Call) {
|
|
633
|
+
print("Call rejected")
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
func ccCallEnded(call: Call) {
|
|
637
|
+
print("Call ended")
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
func ccOutgoingCall(call: Call) {
|
|
641
|
+
print("Outgoing call initiated")
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
func ccCallInitiated(call: Call) {
|
|
645
|
+
print("Call initiated")
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Register
|
|
650
|
+
CometChatCallEvents.addListener("call-listener", CallEventListener())
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## 6. Request Builder Filters
|
|
656
|
+
|
|
657
|
+
### Filter Conversations
|
|
658
|
+
|
|
659
|
+
```swift
|
|
660
|
+
let conversations = CometChatConversations()
|
|
661
|
+
|
|
662
|
+
// Only show user conversations (no groups)
|
|
663
|
+
conversations.set(conversationsRequestBuilder: ConversationsRequest.ConversationsRequestBuilder()
|
|
664
|
+
.set(conversationType: .user)
|
|
665
|
+
.set(limit: 30)
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
// Only show conversations with specific tags
|
|
669
|
+
conversations.set(conversationsRequestBuilder: ConversationsRequest.ConversationsRequestBuilder()
|
|
670
|
+
.set(tags: ["vip", "premium"])
|
|
671
|
+
.set(limit: 30)
|
|
672
|
+
)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Filter Messages
|
|
676
|
+
|
|
677
|
+
```swift
|
|
678
|
+
let messageList = CometChatMessageList()
|
|
679
|
+
|
|
680
|
+
// Only show text and image messages
|
|
681
|
+
messageList.set(messagesRequestBuilder: MessagesRequest.MessageRequestBuilder()
|
|
682
|
+
.set(uid: user.uid ?? "")
|
|
683
|
+
.set(types: [CometChatConstants.MessageType.text, CometChatConstants.MessageType.image])
|
|
684
|
+
.set(limit: 30)
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
// Only show messages from a specific time range
|
|
688
|
+
let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
|
|
689
|
+
messageList.set(messagesRequestBuilder: MessagesRequest.MessageRequestBuilder()
|
|
690
|
+
.set(uid: user.uid ?? "")
|
|
691
|
+
.set(timestamp: Int(startDate.timeIntervalSince1970))
|
|
692
|
+
.set(limit: 50)
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
// Hide deleted messages
|
|
696
|
+
messageList.set(messagesRequestBuilder: MessagesRequest.MessageRequestBuilder()
|
|
697
|
+
.set(uid: user.uid ?? "")
|
|
698
|
+
.hideDeletedMessages(hide: true)
|
|
699
|
+
.set(limit: 30)
|
|
700
|
+
)
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Filter Users
|
|
704
|
+
|
|
705
|
+
```swift
|
|
706
|
+
let users = CometChatUsers()
|
|
707
|
+
|
|
708
|
+
// Only show users with specific role
|
|
709
|
+
users.set(usersRequestBuilder: UsersRequest.UsersRequestBuilder()
|
|
710
|
+
.set(roles: ["admin", "moderator"])
|
|
711
|
+
.set(limit: 30)
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
// Only show friends
|
|
715
|
+
users.set(usersRequestBuilder: UsersRequest.UsersRequestBuilder()
|
|
716
|
+
.friendsOnly(true)
|
|
717
|
+
.set(limit: 30)
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
// Search users
|
|
721
|
+
users.set(usersRequestBuilder: UsersRequest.UsersRequestBuilder()
|
|
722
|
+
.set(searchKeyword: "john")
|
|
723
|
+
.set(limit: 30)
|
|
724
|
+
)
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Filter Groups
|
|
728
|
+
|
|
729
|
+
```swift
|
|
730
|
+
let groups = CometChatGroups()
|
|
731
|
+
|
|
732
|
+
// Only show joined groups
|
|
733
|
+
groups.set(groupsRequestBuilder: GroupsRequest.GroupsRequestBuilder()
|
|
734
|
+
.set(joinedOnly: true)
|
|
735
|
+
.set(limit: 30)
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
// Only show public groups
|
|
739
|
+
groups.set(groupsRequestBuilder: GroupsRequest.GroupsRequestBuilder()
|
|
740
|
+
.set(groupType: .public)
|
|
741
|
+
.set(limit: 30)
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
// Search groups
|
|
745
|
+
groups.set(groupsRequestBuilder: GroupsRequest.GroupsRequestBuilder()
|
|
746
|
+
.set(searchKeyWord: "team")
|
|
747
|
+
.set(limit: 30)
|
|
748
|
+
)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## 7. DataSource Decorator Pattern
|
|
754
|
+
|
|
755
|
+
For advanced customization, use the DataSource decorator pattern:
|
|
756
|
+
|
|
757
|
+
```swift
|
|
758
|
+
class CustomDataSource: DataSourceDecorator {
|
|
759
|
+
|
|
760
|
+
override func getAllMessageTemplates() -> [CometChatMessageTemplate] {
|
|
761
|
+
var templates = super.getAllMessageTemplates()
|
|
762
|
+
|
|
763
|
+
// Add custom template
|
|
764
|
+
let customTemplate = CometChatMessageTemplate(type: "custom_type", category: "custom")
|
|
765
|
+
templates.append(customTemplate)
|
|
766
|
+
|
|
767
|
+
return templates
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
override func getAllMessageTypes() -> [String]? {
|
|
771
|
+
var types = super.getAllMessageTypes() ?? []
|
|
772
|
+
types.append("custom_type")
|
|
773
|
+
return types
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
override func getAllMessageCategories() -> [String]? {
|
|
777
|
+
var categories = super.getAllMessageCategories() ?? []
|
|
778
|
+
categories.append("custom")
|
|
779
|
+
return categories
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
override func getMessageOptions(
|
|
783
|
+
loggedInUser: User,
|
|
784
|
+
messageObject: BaseMessage,
|
|
785
|
+
controller: UIViewController?,
|
|
786
|
+
group: Group?
|
|
787
|
+
) -> [CometChatMessageOption]? {
|
|
788
|
+
var options = super.getMessageOptions(
|
|
789
|
+
loggedInUser: loggedInUser,
|
|
790
|
+
messageObject: messageObject,
|
|
791
|
+
controller: controller,
|
|
792
|
+
group: group
|
|
793
|
+
) ?? []
|
|
794
|
+
|
|
795
|
+
// Add custom option
|
|
796
|
+
let customOption = CometChatMessageOption(
|
|
797
|
+
id: "custom_action",
|
|
798
|
+
title: "Custom Action",
|
|
799
|
+
icon: UIImage(systemName: "star.fill")
|
|
800
|
+
)
|
|
801
|
+
customOption.onClick = { _, _, _, _ in
|
|
802
|
+
print("Custom action triggered")
|
|
803
|
+
}
|
|
804
|
+
options.append(customOption)
|
|
805
|
+
|
|
806
|
+
return options
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Register custom data source
|
|
811
|
+
ChatConfigurator.enable { dataSource in
|
|
812
|
+
return CustomDataSource(dataSource: dataSource)
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Best Practices
|
|
819
|
+
|
|
820
|
+
1. **Keep custom views lightweight** — Complex views affect scroll performance
|
|
821
|
+
2. **Reuse views when possible** — Use cell reuse patterns
|
|
822
|
+
3. **Handle all message types** — Don't forget edge cases
|
|
823
|
+
4. **Test with real data** — Use actual conversations for testing
|
|
824
|
+
5. **Clean up listeners** — Remove event listeners when views are deallocated
|
|
825
|
+
6. **Use weak references** — Avoid retain cycles in closures
|