Skip to main content
The CometChatGroups component displays a searchable list of groups, enabling users to browse, join, and interact with group conversations.

Prerequisites

Before using this component, ensure you have:
  • Completed Getting Started setup
  • User logged in with CometChatUIKit.login()
  • At least one group created in your CometChat dashboard

Usage

Basic Implementation

Display a groups list and open messages when a group is tapped:
import UIKit
import CometChatUIKitSwift
import CometChatSDK

class GroupsViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupGroups()
    }
    
    private func setupGroups() {
        let groups = CometChatGroups()
        
        // Handle group selection
        groups.set(onItemClick: { [weak self] group, indexPath in
            self?.openMessages(for: group)
        })
        
        let navController = UINavigationController(rootViewController: groups)
        navController.modalPresentationStyle = .fullScreen
        present(navController, animated: true)
    }
    
    private func openMessages(for group: Group) {
        let messages = CometChatMessages()
        messages.set(group: group)
        
        if let nav = presentedViewController as? UINavigationController {
            nav.pushViewController(messages, animated: true)
        }
    }
}

Production Implementation

Complete implementation with selection mode, error handling, and navigation:
import UIKit
import CometChatUIKitSwift
import CometChatSDK

class ProductionGroupsViewController: UIViewController {
    
    private var groups: CometChatGroups!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        setupGroups()
        setupEventListeners()
    }
    
    private func setupGroups() {
        // Configure request builder for joined groups only
        let requestBuilder = GroupsRequest.GroupsRequestBuilder(limit: 30)
            .set(joinedOnly: true)
        
        groups = CometChatGroups(groupsRequestBuilder: requestBuilder)
        
        // Handle group tap
        groups.set(onItemClick: { [weak self] group, indexPath in
            self?.openGroupChat(group)
        })
        
        // Handle long press for options
        groups.set(onItemLongClick: { [weak self] group, indexPath in
            self?.showGroupOptions(group)
        })
        
        // Handle back button
        groups.set(onBack: { [weak self] in
            self?.navigationController?.popViewController(animated: true)
        })
        
        // Handle errors
        groups.set(onError: { [weak self] error in
            self?.showError(error)
        })
        
        // Handle empty state
        groups.set(onEmpty: { [weak self] in
            print("No groups available")
        })
        
        // Handle loaded groups
        groups.set(onLoad: { groups in
            print("Loaded \(groups.count) groups")
        })
        
        navigationController?.pushViewController(groups, animated: true)
    }
    
    private func setupEventListeners() {
        CometChatGroupEvents.addListener("groups-vc-listener", self as CometChatGroupEventListener)
    }
    
    private func openGroupChat(_ group: Group) {
        let messages = CometChatMessages()
        messages.set(group: group)
        navigationController?.pushViewController(messages, animated: true)
    }
    
    private func showGroupOptions(_ group: Group) {
        let alert = UIAlertController(title: group.name, message: nil, preferredStyle: .actionSheet)
        
        alert.addAction(UIAlertAction(title: "View Details", style: .default) { [weak self] _ in
            self?.showGroupDetails(group)
        })
        
        alert.addAction(UIAlertAction(title: "Leave Group", style: .destructive) { [weak self] _ in
            self?.leaveGroup(group)
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    private func showGroupDetails(_ group: Group) {
        let details = CometChatGroupDetails()
        details.set(group: group)
        navigationController?.pushViewController(details, animated: true)
    }
    
    private func leaveGroup(_ group: Group) {
        CometChat.leaveGroup(GUID: group.guid) { [weak self] _ in
            DispatchQueue.main.async {
                self?.groups.remove(group: group)
            }
        } onError: { error in
            print("Leave group error: \(error?.errorDescription ?? "")")
        }
    }
    
    private func showError(_ error: CometChatException) {
        let alert = UIAlertController(
            title: "Error",
            message: error.errorDescription,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    deinit {
        CometChatGroupEvents.removeListener("groups-vc-listener")
    }
}

// MARK: - Group Event Listener
extension ProductionGroupsViewController: CometChatGroupEventListener {
    
    func onGroupCreate(group: Group) {
        groups.insert(group: group, at: 0)
    }
    
    func onGroupDelete(group: Group) {
        groups.remove(group: group)
    }
    
    func onGroupMemberJoin(joinedUser: User, joinedGroup: Group) {
        groups.update(group: joinedGroup)
    }
    
    func onGroupMemberLeave(leftUser: User, leftGroup: Group) {
        groups.update(group: leftGroup)
    }
    
    func onGroupMemberAdd(group: Group, members: [GroupMember], addedBy: User) {
        groups.update(group: group)
    }
    
    func onGroupMemberBan(bannedUser: User, bannedGroup: Group) {
        groups.update(group: bannedGroup)
    }
    
    func onGroupMemberUnban(unbannedUserUser: User, unbannedUserGroup: Group) {
        groups.update(group: unbannedUserGroup)
    }
    
    func onGroupMemberKick(kickedUser: User, kickedGroup: Group) {
        groups.update(group: kickedGroup)
    }
    
    func onGroupMemberChangeScope(updatedBy: User, updatedUser: User, scopeChangedTo: CometChat.MemberScope, scopeChangedFrom: CometChat.MemberScope, group: Group) {
        groups.update(group: group)
    }
    
    func onOwnershipChange(group: Group?, member: GroupMember?) {
        if let group = group {
            groups.update(group: group)
        }
    }
}

Actions

Actions let you customize component behavior through callbacks.
ActionDescription
set(onItemClick:)Triggered when a group is tapped
set(onItemLongClick:)Triggered on long press
set(onBack:)Triggered when back button is pressed
set(onSelection:)Triggered when groups are selected (selection mode)
set(onError:)Triggered when an error occurs
set(onEmpty:)Triggered when the list is empty
set(onLoad:)Triggered when groups are loaded
let groups = CometChatGroups()

groups.set(onItemClick: { group, indexPath in
    print("Tapped: \(group.name ?? "")")
})

groups.set(onItemLongClick: { group, indexPath in
    print("Long pressed: \(group.name ?? "")")
})

groups.set(onBack: {
    print("Back button pressed")
})

groups.set(onSelection: { selectedGroups in
    print("Selected \(selectedGroups.count) groups")
})

groups.set(onError: { error in
    print("Error: \(error.errorDescription)")
})

groups.set(onEmpty: {
    print("No groups found")
})

groups.set(onLoad: { groups in
    print("Loaded \(groups.count) groups")
})

Filters

Filter the groups list using GroupsRequestBuilder.
MethodTypeDescription
setLimitIntMaximum groups per request
setSearchKeywordStringFilter by search term
joinedOnlyBoolShow only joined groups
setTags[String]Filter by tags
withTagsBoolInclude tag information

Show Only Joined Groups

let requestBuilder = GroupsRequest.GroupsRequestBuilder(limit: 30)
    .set(joinedOnly: true)

let groups = CometChatGroups(groupsRequestBuilder: requestBuilder)

Search Groups

let searchBuilder = GroupsRequest.GroupsRequestBuilder(limit: 20)
    .set(searchKeyword: "design")

let groups = CometChatGroups(groupsRequestBuilder: searchBuilder)

Filter by Tags

let tagBuilder = GroupsRequest.GroupsRequestBuilder(limit: 30)
    .set(tags: ["engineering", "marketing"])
    .set(withTags: true)

let groups = CometChatGroups(groupsRequestBuilder: tagBuilder)

Events

Listen for group-related events across your app.
EventDescription
onGroupCreateGroup was created
onGroupDeleteGroup was deleted
onGroupMemberJoinUser joined a group
onGroupMemberLeaveUser left a group
onGroupMemberAddMembers were added
onGroupMemberBanMember was banned
onGroupMemberUnbanMember was unbanned
onGroupMemberKickMember was kicked
onGroupMemberChangeScopeMember scope changed
onOwnershipChangeGroup ownership transferred

Add Event Listener

import UIKit
import CometChatUIKitSwift
import CometChatSDK

class GroupEventsViewController: UIViewController {
    
    private let listenerID = "group-events-listener"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        CometChatGroupEvents.addListener(listenerID, self as CometChatGroupEventListener)
    }
    
    deinit {
        CometChatGroupEvents.removeListener(listenerID)
    }
}

extension GroupEventsViewController: CometChatGroupEventListener {
    
    func onGroupCreate(group: Group) {
        print("Group created: \(group.name ?? "")")
    }
    
    func onGroupDelete(group: Group) {
        print("Group deleted: \(group.name ?? "")")
    }
    
    func onGroupMemberJoin(joinedUser: User, joinedGroup: Group) {
        print("\(joinedUser.name ?? "") joined \(joinedGroup.name ?? "")")
    }
    
    func onGroupMemberLeave(leftUser: User, leftGroup: Group) {
        print("\(leftUser.name ?? "") left \(leftGroup.name ?? "")")
    }
    
    func onGroupMemberAdd(group: Group, members: [GroupMember], addedBy: User) {
        print("\(members.count) members added to \(group.name ?? "")")
    }
    
    func onGroupMemberBan(bannedUser: User, bannedGroup: Group) {
        print("\(bannedUser.name ?? "") banned from \(bannedGroup.name ?? "")")
    }
    
    func onGroupMemberUnban(unbannedUserUser: User, unbannedUserGroup: Group) {
        print("\(unbannedUserUser.name ?? "") unbanned from \(unbannedUserGroup.name ?? "")")
    }
    
    func onGroupMemberKick(kickedUser: User, kickedGroup: Group) {
        print("\(kickedUser.name ?? "") kicked from \(kickedGroup.name ?? "")")
    }
    
    func onGroupMemberChangeScope(updatedBy: User, updatedUser: User, scopeChangedTo: CometChat.MemberScope, scopeChangedFrom: CometChat.MemberScope, group: Group) {
        print("\(updatedUser.name ?? "") scope changed to \(scopeChangedTo)")
    }
    
    func onOwnershipChange(group: Group?, member: GroupMember?) {
        print("Ownership transferred to \(member?.name ?? "")")
    }
}

Emit Events

// Emit group created event
CometChatGroupEvents.emitOnGroupCreate(group: group)

// Emit group deleted event
CometChatGroupEvents.emitOnGroupDelete(group: group)

// Emit member joined event
CometChatGroupEvents.emitOnGroupMemberJoin(joinedUser: user, joinedGroup: group)

// Emit member left event
CometChatGroupEvents.emitOnGroupMemberLeave(leftUser: user, leftGroup: group)

// Emit members added event
CometChatGroupEvents.emitOnGroupMemberAdd(group: group, members: members, addedBy: user)

// Emit member banned event
CometChatGroupEvents.emitOnGroupMemberBan(bannedUser: user, bannedGroup: group, bannedBy: admin)

// Emit member unbanned event
CometChatGroupEvents.emitOnGroupMemberUnban(unbannedUserUser: user, unbannedUserGroup: group, unbannedBy: admin)

// Emit member kicked event
CometChatGroupEvents.emitOnGroupMemberKick(kickedUser: user, kickedGroup: group, kickedBy: admin)

Styling

Customize the appearance using GroupsStyle.

Global Styling

Apply styles to all CometChatGroups instances:
// Configure avatar style
let avatarStyle = AvatarStyle()
avatarStyle.backgroundColor = UIColor(hex: "#FBAA75")
avatarStyle.cornerRadius = CometChatCornerStyle(cornerRadius: 8)

// Configure groups style
CometChatGroups.style.titleColor = UIColor(hex: "#F76808")
CometChatGroups.style.titleFont = UIFont.systemFont(ofSize: 34, weight: .bold)
CometChatGroups.avatarStyle = avatarStyle

Instance Styling

Apply styles to a specific instance:
let avatarStyle = AvatarStyle()
avatarStyle.backgroundColor = UIColor(hex: "#FBAA75")
avatarStyle.cornerRadius = CometChatCornerStyle(cornerRadius: 20)

let groupsStyle = GroupsStyle()
groupsStyle.titleColor = UIColor(hex: "#F76808")
groupsStyle.titleFont = UIFont.systemFont(ofSize: 34, weight: .bold)
groupsStyle.backgroundColor = .systemBackground
groupsStyle.listItemTitleTextColor = .label
groupsStyle.listItemSubTitleTextColor = .secondaryLabel

let groups = CometChatGroups()
groups.style = groupsStyle
groups.avatarStyle = avatarStyle

Style Properties

PropertyDescriptionCode
listItemSelectedImageCheck box image when a list item is selectedlistItemSelectedImage
listItemDeSelectedImageCheck box image when a list item is deselectedlistItemDeSelectedImage
searchIconTintColorTint color for the search iconsearchIconTintColor
searchBarStyleStyle of the search barsearchBarStyle
searchTintColorTint color for the search barsearchTintColor
searchBarTintColorBackground color of the search barsearchBarTintColor
searchBarPlaceholderTextColorPlaceholder text color for the search barsearchBarPlaceholderTextColor
searchBarPlaceholderTextFontFont for the placeholder text in the search barsearchBarPlaceholderTextFont
searchBarTextColorColor of the text entered in the search barsearchBarTextColor
searchBarTextFontFont for the text in the search barsearchBarTextFont
searchBarBackgroundColorBackground color of the search barsearchBarBackgroundColor
searchBarCancelIconTintColorTint color for the cancel icon in the search barsearchBarCancelIconTintColor
searchBarCrossIconTintColorTint color for the cross icon in the search barsearchBarCrossIconTintColor
backgroundColorBackground color of the overall viewbackgroundColor
borderWidthWidth of the border around the viewborderWidth
borderColorColor of the border around the viewborderColor
cornerRadiusCorner radius settings for the viewcornerRadius
titleColorColor for the title texttitleColor
titleFontFont used for the title texttitleFont
largeTitleColorColor for the large title textlargeTitleColor
largeTitleFontFont used for the large title textlargeTitleFont
navigationBarTintColorBackground color of the navigation barnavigationBarTintColor
navigationBarItemsTintColorTint color for items in the navigation barnavigationBarItemsTintColor
errorTitleTextFontFont used for the error title texterrorTitleTextFont
errorTitleTextColorColor of the error title texterrorTitleTextColor
errorSubTitleFontFont used for the error subtitle texterrorSubTitleFont
errorSubTitleTextColorColor of the error subtitle texterrorSubTitleTextColor
retryButtonTextColorColor for the retry button textretryButtonTextColor
retryButtonTextFontFont used for the retry button textretryButtonTextFont
retryButtonBackgroundColorBackground color for the retry buttonretryButtonBackgroundColor
retryButtonBorderColorBorder color for the retry buttonretryButtonBorderColor
retryButtonBorderWidthWidth of the border around the retry buttonretryButtonBorderWidth
retryButtonCornerRadiusCorner radius settings for the retry buttonretryButtonCornerRadius
emptyTitleTextFontFont used for the empty state title textemptyTitleTextFont
emptyTitleTextColorColor of the empty state title textemptyTitleTextColor
emptySubTitleFontFont used for the empty state subtitle textemptySubTitleFont
emptySubTitleTextColorColor of the empty state subtitle textemptySubTitleTextColor
tableViewSeparatorColor of the table view separatortableViewSeparator
listItemTitleTextColorColor of the title text in list itemslistItemTitleTextColor
listItemTitleFontFont used for the title text in list itemslistItemTitleFont
listItemSubTitleTextColorColor of the subtitle text in list itemslistItemSubTitleTextColor
listItemSubTitleFontFont used for the subtitle text in list itemslistItemSubTitleFont
listItemBackgroundBackground color for list itemslistItemBackground
listItemSelectedBackgroundBackground color for list items if selectedlistItemSelectedBackground
listItemBorderWidthWidth of the border around list itemslistItemBorderWidth
listItemBorderColorColor of the border around list itemslistItemBorderColor
listItemCornerRadiusCorner radius settings for list itemslistItemCornerRadius
listItemSelectionImageTintTint color for the selection image in list itemslistItemSelectionImageTint
listItemDeSelectedImageTintTint color for the deselected image in list itemslistItemDeSelectedImageTint
passwordGroupImageTintColorTint color for the password group imagepasswordGroupImageTintColor
passwordGroupImageBackgroundColorBackground color for the password group imagepasswordGroupImageBackgroundColor
privateGroupImageTintColorTint color for the private group imageprivateGroupImageTintColor
privateGroupImageBackgroundColorBackground color for the private group imageprivateGroupImageBackgroundColor
privateGroupIconImage for a private group iconprivateGroupIcon
protectedGroupIconImage for a protected group iconprotectedGroupIcon

Functionality

Configure component behavior with these properties and methods:
MethodDescriptionCode
set(groupsRequestBuilder:)Sets the request builder for fetching groupsset(groupsRequestBuilder: requestBuilder)
set(searchRequestBuilder:)Sets the request builder for searching groupsset(searchRequestBuilder: searchRequestBuilder)
set(searchKeyword:)Sets the search keyword to filter groupsset(searchKeyword: "group_name")
hideErrorViewHides the error state viewhideErrorView = true
hideNavigationBarHides or shows the navigation barhideNavigationBar = true
hideSearchHides the search barhideSearch = true
hideBackButtonHides the back buttonhideBackButton = true
hideLoadingStateHides the loading state indicatorhideLoadingState = true
hideReceiptsHides message read/delivery receiptshideReceipts = true
hideDeleteConversationOptionHides the option to delete a conversationhideDeleteConversationOption = true
hideUserStatusHides the online/offline status of usershideUserStatus = true
hideGroupTypeHides the group type (private/public)hideGroupType = true
let groups = CometChatGroups()
groups.hideSearch = true
groups.hideGroupType = false
groups.hideBackButton = true
groups.hideErrorView = false
groups.hideLoadingState = false

Custom Views

SetOptions

You can define custom options for each group using .set(options:). This method allows you to return an array of CometChatGroupOption based on the group object.
cometChatGroups.set(options: { group in  
    return [CometChatGroupOptions]  
})  

AddOptions

You can dynamically add options to groups using .add(options:). This method lets you return additional CometChatGroupOption elements.
cometChatGroups.add(options: { group in  
    return [ArchiveOption()]  
})  

SetListItemView

With this function, you can assign a custom ListItem to the groups Component.
let cometChatGroups = CometChatGroups()
cometChatGroups.set(listItemView: { group in
    let view = GroupCellView()
    return view
})

Custom List Item

Replace the default group cell with a custom view:
let groups = CometChatGroups()
groups.set(listItemView: { group in
    let customView = CustomGroupCell()
    customView.configure(with: group)
    return customView
})
import UIKit
import CometChatSDK

class CustomGroupCell: UIView {
    
    private let avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        imageView.layer.cornerRadius = 20
        imageView.clipsToBounds = true
        imageView.backgroundColor = .systemGray5
        return imageView
    }()
    
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = .systemFont(ofSize: 16, weight: .semibold)
        label.textColor = .label
        return label
    }()
    
    private let membersLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = .systemFont(ofSize: 14)
        label.textColor = .secondaryLabel
        return label
    }()
    
    private let joinButton: UIButton = {
        let button = UIButton(type: .system)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("JOIN", for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 12, weight: .bold)
        button.layer.cornerRadius = 12
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.systemBlue.cgColor
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }
    
    private func setupView() {
        addSubview(avatarImageView)
        addSubview(titleLabel)
        addSubview(membersLabel)
        addSubview(joinButton)
        
        NSLayoutConstraint.activate([
            avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            avatarImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 40),
            avatarImageView.heightAnchor.constraint(equalToConstant: 40),
            
            titleLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 12),
            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
            titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: joinButton.leadingAnchor, constant: -8),
            
            membersLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 12),
            membersLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            membersLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
            
            joinButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            joinButton.centerYAnchor.constraint(equalTo: centerYAnchor),
            joinButton.widthAnchor.constraint(equalToConstant: 60),
            joinButton.heightAnchor.constraint(equalToConstant: 24)
        ])
    }
    
    func configure(with group: Group) {
        titleLabel.text = group.name
        membersLabel.text = "\(group.membersCount) members"
        
        if let iconURL = group.icon, let url = URL(string: iconURL) {
            URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
                if let data = data, let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self?.avatarImageView.image = image
                    }
                }
            }.resume()
        }
        
        let isJoined = group.hasJoined
        joinButton.setTitle(isJoined ? "JOINED" : "JOIN", for: .normal)
        joinButton.setTitleColor(isJoined ? .systemGray : .systemBlue, for: .normal)
        joinButton.layer.borderColor = isJoined ? UIColor.systemGray.cgColor : UIColor.systemBlue.cgColor
    }
}

SetLeadingView

You can modify the leading view of a group cell using .set(leadingView:).
cometChatGroups.set(leadingView: { group in  
    let view = CustomLeadingView()
    return view  
})  
You can create a CustomLeadingView as a custom UIView. Which we will inflate in setLeadingView():
import UIKit

class CustomLeadingView: UIView {

    private let iconImageView: UIImageView = {
        let imageView = UIImageView(image: UIImage(systemName: "person.2.fill"))
        imageView.tintColor = .white
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()

    private let joinButton: UIButton = {
        let button = UIButton()
        button.setTitle("Join", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
        button.backgroundColor = .orange
        button.layer.cornerRadius = 8
        return button
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupView() {
        backgroundColor = .purple
        layer.cornerRadius = 12

        addSubview(iconImageView)
        addSubview(joinButton)

        iconImageView.translatesAutoresizingMaskIntoConstraints = false
        joinButton.translatesAutoresizingMaskIntoConstraints = false
    }
}

SetTitleView

You can customize the title view of a group cell using .set(titleView:).
cometChatGroups.set(titleView: { group in  
    let view = CustomTitleView()
    return view
})  
You can create a CustomTitleView as a custom UIView. Which we will inflate in setTitleView():
class CustomTitleView: UIView {

    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = "Artistic Design"
        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
        label.textColor = .black
        return label
    }()

    private let publicBadge: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        view.layer.cornerRadius = 10

        let icon = UIImageView(image: UIImage(systemName: "person.3.fill"))
        icon.tintColor = .white
        icon.contentMode = .scaleAspectFit

        let label = UILabel()
        label.text = "Public"
        label.font = UIFont.systemFont(ofSize: 12, weight: .bold)
        label.textColor = .white

        let stackView = UIStackView(arrangedSubviews: [icon, label])
        stackView.spacing = 4
        stackView.alignment = .center
        stackView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            view.widthAnchor.constraint(equalToConstant: 60),
            view.heightAnchor.constraint(equalToConstant: 20)
        ])
        
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupView() {
        addSubview(titleLabel)
        addSubview(publicBadge)

        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        publicBadge.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),

            publicBadge.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 6),
            publicBadge.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
}

SetTrailView

You can modify the trailing view of a group cell using .set(trailView:).
cometChatGroups.set(trailView: { group in  
    let view = CustomTrailView() 
    return view  
})  
You can create a CustomTrailView as a custom UIView. Which we will inflate in setTrailView():
import UIKit

class CustomTrailView: UIView {

    private let joinedLabel: UILabel = {
        let label = UILabel()
        label.text = "JOINED"
        label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
        label.textColor = UIColor.purple
        return label
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupView() {
        backgroundColor = UIColor.purple.withAlphaComponent(0.1)
        layer.cornerRadius = 16

        addSubview(joinedLabel)
        joinedLabel.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            joinedLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            joinedLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
            widthAnchor.constraint(equalToConstant: 80),
            heightAnchor.constraint(equalToConstant: 32)
        ])
    }
}

SetSubTitleView

You can customize the subtitle view for each group item to meet your requirements:
cometChatGroup.set(subtitleView: { group in
   let view = CustomSubtitleView()
   return view
})
You can seamlessly integrate this CustomSubtitleView UIView file into the .set(subtitleView:) method within CometChatGroups:
import UIKit

class CustomSubtitleView: UILabel {
    
    init(membersCount: Int) {
        super.init(frame: .zero)
        self.text = "\(membersCount) members • \("group_description")"
        self.textColor = UIColor.gray
        self.font = UIFont.systemFont(ofSize: 14)
        self.numberOfLines = 1
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

SetLoadingView

You can set a custom loading view using .set(loadingView:). This method accepts a UIView to display while data is being fetched.
let loadingIndicator = UIActivityIndicatorView(style: .medium)  
loadingIndicator.startAnimating()  
cometChatGroups.set(loadingView: loadingIndicator)  

SetErrorView

You can customize the error view using .set(errorView:). This method accepts a UIView that appears when an error occurs.
let errorLabel = UILabel()  
errorLabel.text = "Something went wrong!"  
errorLabel.textColor = .red  
cometChatGroups.set(errorView: errorLabel)  

SetEmptyView

You can customize the empty state view using .set(emptyView:). This method accepts a UIView that appears when no groups are available.
let emptyLabel = UILabel()  
emptyLabel.text = "No groups found"  
emptyLabel.textColor = .gray  
emptyLabel.textAlignment = .center  
cometChatGroups.set(emptyView: emptyLabel)  

Group Options

Add custom swipe actions to group cells.

Set Options

Replace default options:
groups.set(options: { group in
    let leaveOption = CometChatGroupOption(
        id: "leave",
        title: "Leave",
        icon: UIImage(systemName: "arrow.right.square"),
        backgroundColor: .systemRed
    ) { group in
        // Handle leave action
    }
    return [leaveOption]
})

Add Options

Add options to existing ones:
groups.add(options: { group in
    let archiveOption = CometChatGroupOption(
        id: "archive",
        title: "Archive",
        icon: UIImage(systemName: "archivebox"),
        backgroundColor: .systemOrange
    ) { group in
        // Handle archive action
    }
    return [archiveOption]
})

Component Structure

ComponentDescription
CometChatListBaseContainer with navigation bar and search
CometChatListItemIndividual group cell

Troubleshooting

IssueSolution
Groups not loadingVerify user is logged in and has proper permissions
Search not workingCheck searchRequestBuilder configuration
Events not firingEnsure listener is added before actions occur
Styling not appliedApply styles before presenting the component
Custom views not showingReturn non-nil UIView from closure