@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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -17
  3. package/bin/install.js +389 -43
  4. package/package.json +19 -9
  5. package/skills/cometchat/SKILL.md +1066 -265
  6. package/skills/cometchat-android-v5/SKILL.md +120 -0
  7. package/skills/cometchat-android-v5-components/SKILL.md +430 -0
  8. package/skills/cometchat-android-v5-core/SKILL.md +468 -0
  9. package/skills/cometchat-android-v5-customization/SKILL.md +305 -0
  10. package/skills/cometchat-android-v5-extensions/SKILL.md +124 -0
  11. package/skills/cometchat-android-v5-features/SKILL.md +130 -0
  12. package/skills/cometchat-android-v5-placement/SKILL.md +276 -0
  13. package/skills/cometchat-android-v5-production/SKILL.md +94 -0
  14. package/skills/cometchat-android-v5-push/SKILL.md +620 -0
  15. package/skills/cometchat-android-v5-testing/SKILL.md +187 -0
  16. package/skills/cometchat-android-v5-theming/SKILL.md +166 -0
  17. package/skills/cometchat-android-v5-troubleshooting/SKILL.md +73 -0
  18. package/skills/cometchat-android-v6/SKILL.md +107 -0
  19. package/skills/cometchat-android-v6-builder-settings/SKILL.md +145 -0
  20. package/skills/cometchat-android-v6-compose-components/SKILL.md +202 -0
  21. package/skills/cometchat-android-v6-compose-customization/SKILL.md +280 -0
  22. package/skills/cometchat-android-v6-compose-placement/SKILL.md +213 -0
  23. package/skills/cometchat-android-v6-compose-theming/SKILL.md +224 -0
  24. package/skills/cometchat-android-v6-core/SKILL.md +354 -0
  25. package/skills/cometchat-android-v6-events/SKILL.md +212 -0
  26. package/skills/cometchat-android-v6-extensions/SKILL.md +191 -0
  27. package/skills/cometchat-android-v6-features/SKILL.md +130 -0
  28. package/skills/cometchat-android-v6-kotlin-components/SKILL.md +197 -0
  29. package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +268 -0
  30. package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +248 -0
  31. package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +202 -0
  32. package/skills/cometchat-android-v6-production/SKILL.md +187 -0
  33. package/skills/cometchat-android-v6-push/SKILL.md +274 -0
  34. package/skills/cometchat-android-v6-testing/SKILL.md +279 -0
  35. package/skills/cometchat-android-v6-troubleshooting/SKILL.md +249 -0
  36. package/skills/cometchat-angular-components/SKILL.md +706 -0
  37. package/skills/cometchat-angular-core/SKILL.md +444 -0
  38. package/skills/cometchat-angular-customization/SKILL.md +424 -0
  39. package/skills/cometchat-angular-features/SKILL.md +447 -0
  40. package/skills/cometchat-angular-patterns/SKILL.md +447 -0
  41. package/skills/cometchat-angular-placement/SKILL.md +685 -0
  42. package/skills/cometchat-angular-production/SKILL.md +415 -0
  43. package/skills/cometchat-angular-theming/SKILL.md +365 -0
  44. package/skills/cometchat-angular-troubleshooting/SKILL.md +329 -0
  45. package/skills/cometchat-astro-patterns/SKILL.md +3 -3
  46. package/skills/cometchat-components/SKILL.md +11 -0
  47. package/skills/cometchat-core/SKILL.md +11 -6
  48. package/skills/cometchat-customization/references/component-catalog.md +1 -1
  49. package/skills/cometchat-features/SKILL.md +176 -108
  50. package/skills/cometchat-flutter-v5/SKILL.md +167 -0
  51. package/skills/cometchat-flutter-v5-calls/SKILL.md +192 -0
  52. package/skills/cometchat-flutter-v5-conversations/SKILL.md +117 -0
  53. package/skills/cometchat-flutter-v5-core/SKILL.md +221 -0
  54. package/skills/cometchat-flutter-v5-customization/SKILL.md +227 -0
  55. package/skills/cometchat-flutter-v5-events/SKILL.md +155 -0
  56. package/skills/cometchat-flutter-v5-messages/SKILL.md +258 -0
  57. package/skills/cometchat-flutter-v5-production/SKILL.md +207 -0
  58. package/skills/cometchat-flutter-v5-push/SKILL.md +284 -0
  59. package/skills/cometchat-flutter-v5-theming/SKILL.md +160 -0
  60. package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +129 -0
  61. package/skills/cometchat-flutter-v5-users-groups/SKILL.md +151 -0
  62. package/skills/cometchat-flutter-v6/SKILL.md +445 -0
  63. package/skills/cometchat-flutter-v6-calls/SKILL.md +333 -0
  64. package/skills/cometchat-flutter-v6-components/SKILL.md +365 -0
  65. package/skills/cometchat-flutter-v6-conversations/SKILL.md +210 -0
  66. package/skills/cometchat-flutter-v6-core/SKILL.md +305 -0
  67. package/skills/cometchat-flutter-v6-customization/SKILL.md +666 -0
  68. package/skills/cometchat-flutter-v6-events/SKILL.md +216 -0
  69. package/skills/cometchat-flutter-v6-features/SKILL.md +406 -0
  70. package/skills/cometchat-flutter-v6-messages/SKILL.md +333 -0
  71. package/skills/cometchat-flutter-v6-migration/SKILL.md +381 -0
  72. package/skills/cometchat-flutter-v6-placement/SKILL.md +398 -0
  73. package/skills/cometchat-flutter-v6-production/SKILL.md +632 -0
  74. package/skills/cometchat-flutter-v6-theming/SKILL.md +260 -0
  75. package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +726 -0
  76. package/skills/cometchat-flutter-v6-users-groups/SKILL.md +216 -0
  77. package/skills/cometchat-ios/SKILL.md +323 -0
  78. package/skills/cometchat-ios-components/SKILL.md +651 -0
  79. package/skills/cometchat-ios-core/SKILL.md +822 -0
  80. package/skills/cometchat-ios-customization/SKILL.md +825 -0
  81. package/skills/cometchat-ios-features/SKILL.md +725 -0
  82. package/skills/cometchat-ios-placement/SKILL.md +750 -0
  83. package/skills/cometchat-ios-production/SKILL.md +730 -0
  84. package/skills/cometchat-ios-push/SKILL.md +582 -0
  85. package/skills/cometchat-ios-theming/SKILL.md +631 -0
  86. package/skills/cometchat-ios-troubleshooting/SKILL.md +983 -0
  87. package/skills/cometchat-native-bare-patterns/SKILL.md +470 -0
  88. package/skills/cometchat-native-components/SKILL.md +594 -0
  89. package/skills/cometchat-native-core/SKILL.md +407 -0
  90. package/skills/cometchat-native-customization/SKILL.md +594 -0
  91. package/skills/cometchat-native-expo-patterns/SKILL.md +485 -0
  92. package/skills/cometchat-native-features/SKILL.md +608 -0
  93. package/skills/cometchat-native-placement/SKILL.md +500 -0
  94. package/skills/cometchat-native-production/SKILL.md +694 -0
  95. package/skills/cometchat-native-push/SKILL.md +593 -0
  96. package/skills/cometchat-native-testing/SKILL.md +583 -0
  97. package/skills/cometchat-native-theming/SKILL.md +656 -0
  98. package/skills/cometchat-native-troubleshooting/SKILL.md +433 -0
  99. package/skills/cometchat-nextjs-patterns/SKILL.md +5 -5
  100. package/skills/cometchat-production/SKILL.md +60 -0
  101. 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