Prerequisites
Before implementing this feature, ensure you have:- Completed Getting Started setup
- CometChat UIKit v5+ installed
- User logged in with
CometChatUIKit.login() - Navigation controller configured
Overview
The user details screen provides:- Profile Display - Avatar, name, and online status
- Messaging - Start one-on-one chats
- Calling - Audio and video call buttons
- Block/Unblock - Manage user relationships
- Delete Conversation - Remove chat history
Basic Implementation
Create a simple user details screen:Report incorrect code
Copy
Ask AI
import UIKit
import CometChatUIKitSwift
import CometChatSDK
class UserDetailsViewController: UIViewController {
private var user: User?
convenience init(user: User) {
self.init()
self.user = user
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupUI()
}
private func setupUI() {
guard let user = user else { return }
// Avatar
let avatar = CometChatAvatar()
avatar.set(user: user)
avatar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(avatar)
// Name label
let nameLabel = UILabel()
nameLabel.text = user.name
nameLabel.font = .systemFont(ofSize: 24, weight: .bold)
nameLabel.textAlignment = .center
nameLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameLabel)
// Status label
let statusLabel = UILabel()
statusLabel.text = user.status == .online ? "Online" : "Offline"
statusLabel.textColor = user.status == .online ? .systemGreen : .secondaryLabel
statusLabel.font = .systemFont(ofSize: 14)
statusLabel.textAlignment = .center
statusLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(statusLabel)
// Message button
let messageButton = UIButton(type: .system)
messageButton.setTitle("Message", for: .normal)
messageButton.setImage(UIImage(systemName: "message.fill"), for: .normal)
messageButton.addTarget(self, action: #selector(openChat), for: .touchUpInside)
messageButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(messageButton)
// Block button
let blockButton = UIButton(type: .system)
blockButton.setTitle("Block User", for: .normal)
blockButton.setTitleColor(.systemRed, for: .normal)
blockButton.addTarget(self, action: #selector(blockUser), for: .touchUpInside)
blockButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blockButton)
NSLayoutConstraint.activate([
avatar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
avatar.centerXAnchor.constraint(equalTo: view.centerXAnchor),
avatar.widthAnchor.constraint(equalToConstant: 100),
avatar.heightAnchor.constraint(equalToConstant: 100),
nameLabel.topAnchor.constraint(equalTo: avatar.bottomAnchor, constant: 16),
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
statusLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
messageButton.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32),
messageButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
blockButton.topAnchor.constraint(equalTo: messageButton.bottomAnchor, constant: 24),
blockButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
@objc private func openChat() {
guard let user = user else { return }
let messages = CometChatMessages()
messages.set(user: user)
navigationController?.pushViewController(messages, animated: true)
}
@objc private func blockUser() {
guard let user = user else { return }
CometChat.blockUsers([user.uid ?? ""]) { [weak self] _ in
DispatchQueue.main.async {
self?.showAlert(title: "Blocked", message: "\(user.name ?? "User") has been blocked")
}
} onError: { error in
print("Block error: \(error?.errorDescription ?? "")")
}
}
private func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
Production Implementation
Complete user details screen with all features and event handling:Report incorrect code
Copy
Ask AI
import UIKit
import CometChatUIKitSwift
import CometChatSDK
#if canImport(CometChatCallsSDK)
import CometChatCallsSDK
#endif
class ProductionUserDetailsViewController: UIViewController {
// MARK: - Properties
private var user: User?
private var isBlocked: Bool = false
private let listenerID = "user-details-listener"
// MARK: - UI Components
private let scrollView = UIScrollView()
private let contentView = UIView()
private let avatar = CometChatAvatar()
private let nameLabel = UILabel()
private let statusLabel = UILabel()
private let buttonStackView = UIStackView()
private let messageButton = UIButton(type: .system)
private let audioCallButton = UIButton(type: .system)
private let videoCallButton = UIButton(type: .system)
private let blockButton = UIButton(type: .system)
private let deleteButton = UIButton(type: .system)
// MARK: - Initialization
convenience init(user: User) {
self.init()
self.user = user
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupUI()
configureWithUser()
checkBlockStatus()
addEventListeners()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBar()
}
deinit {
removeEventListeners()
}
// MARK: - Setup
private func setupNavigationBar() {
title = "User Details"
navigationController?.navigationBar.prefersLargeTitles = false
}
private func setupUI() {
// Scroll View
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
// Avatar
avatar.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(avatar)
// Name Label
nameLabel.font = .systemFont(ofSize: 24, weight: .bold)
nameLabel.textAlignment = .center
nameLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(nameLabel)
// Status Label
statusLabel.font = .systemFont(ofSize: 14)
statusLabel.textAlignment = .center
statusLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(statusLabel)
// Button Stack
buttonStackView.axis = .horizontal
buttonStackView.spacing = 24
buttonStackView.distribution = .equalSpacing
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(buttonStackView)
// Message Button
configureButton(messageButton, title: "Message", icon: "message.fill", action: #selector(openChat))
buttonStackView.addArrangedSubview(messageButton)
// Call Buttons (conditional)
#if canImport(CometChatCallsSDK)
configureButton(audioCallButton, title: "Audio", icon: "phone.fill", action: #selector(startAudioCall))
configureButton(videoCallButton, title: "Video", icon: "video.fill", action: #selector(startVideoCall))
buttonStackView.addArrangedSubview(audioCallButton)
buttonStackView.addArrangedSubview(videoCallButton)
#endif
// Block Button
blockButton.setTitle("Block User", for: .normal)
blockButton.setTitleColor(.systemRed, for: .normal)
blockButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
blockButton.addTarget(self, action: #selector(toggleBlock), for: .touchUpInside)
blockButton.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(blockButton)
// Delete Button
deleteButton.setTitle("Delete Conversation", for: .normal)
deleteButton.setTitleColor(.systemRed, for: .normal)
deleteButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
deleteButton.addTarget(self, action: #selector(deleteConversation), for: .touchUpInside)
deleteButton.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(deleteButton)
setupConstraints()
}
private func configureButton(_ button: UIButton, title: String, icon: String, action: Selector) {
button.setTitle(title, for: .normal)
button.setImage(UIImage(systemName: icon), for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 12)
button.tintColor = .systemBlue
button.configuration = .plain()
button.configuration?.imagePlacement = .top
button.configuration?.imagePadding = 8
button.addTarget(self, action: action, for: .touchUpInside)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
avatar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 40),
avatar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
avatar.widthAnchor.constraint(equalToConstant: 100),
avatar.heightAnchor.constraint(equalToConstant: 100),
nameLabel.topAnchor.constraint(equalTo: avatar.bottomAnchor, constant: 16),
nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
statusLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
statusLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
buttonStackView.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32),
buttonStackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
blockButton.topAnchor.constraint(equalTo: buttonStackView.bottomAnchor, constant: 48),
blockButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
deleteButton.topAnchor.constraint(equalTo: blockButton.bottomAnchor, constant: 16),
deleteButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
deleteButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -40)
])
}
// MARK: - Configuration
private func configureWithUser() {
guard let user = user else { return }
avatar.set(user: user)
nameLabel.text = user.name
updateStatusLabel(user: user)
}
private func updateStatusLabel(user: User) {
if isBlocked {
statusLabel.text = "Blocked"
statusLabel.textColor = .systemRed
} else {
statusLabel.text = user.status == .online ? "Online" : "Offline"
statusLabel.textColor = user.status == .online ? .systemGreen : .secondaryLabel
}
}
private func checkBlockStatus() {
guard let uid = user?.uid else { return }
let request = BlockedUserRequest.BlockedUserRequestBuilder()
.set(limit: 100)
.build()
request.fetchNext { [weak self] blockedUsers in
let isBlocked = blockedUsers?.contains { $0.uid == uid } ?? false
DispatchQueue.main.async {
self?.isBlocked = isBlocked
self?.updateBlockButton()
self?.updateStatusLabel(user: self?.user ?? User())
}
} onError: { error in
print("Error checking block status: \(error?.errorDescription ?? "")")
}
}
private func updateBlockButton() {
let title = isBlocked ? "Unblock User" : "Block User"
blockButton.setTitle(title, for: .normal)
}
// MARK: - Actions
@objc private func openChat() {
guard let user = user else { return }
if isBlocked {
showAlert(title: "User Blocked", message: "Unblock this user to send messages")
return
}
let messages = CometChatMessages()
messages.set(user: user)
navigationController?.pushViewController(messages, animated: true)
}
#if canImport(CometChatCallsSDK)
@objc private func startAudioCall() {
guard let user = user else { return }
if isBlocked {
showAlert(title: "User Blocked", message: "Unblock this user to make calls")
return
}
let call = Call(receiverId: user.uid ?? "", callType: .audio, receiverType: .user)
CometChat.initiateCall(call: call) { call in
print("Audio call initiated: \(call?.sessionID ?? "")")
} onError: { [weak self] error in
self?.showAlert(title: "Call Failed", message: error?.errorDescription ?? "Unable to start call")
}
}
@objc private func startVideoCall() {
guard let user = user else { return }
if isBlocked {
showAlert(title: "User Blocked", message: "Unblock this user to make calls")
return
}
let call = Call(receiverId: user.uid ?? "", callType: .video, receiverType: .user)
CometChat.initiateCall(call: call) { call in
print("Video call initiated: \(call?.sessionID ?? "")")
} onError: { [weak self] error in
self?.showAlert(title: "Call Failed", message: error?.errorDescription ?? "Unable to start call")
}
}
#endif
@objc private func toggleBlock() {
if isBlocked {
unblockUser()
} else {
showBlockConfirmation()
}
}
private func showBlockConfirmation() {
let alert = UIAlertController(
title: "Block User",
message: "Are you sure you want to block \(user?.name ?? "this user")? They won't be able to send you messages or call you.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alert.addAction(UIAlertAction(title: "Block", style: .destructive) { [weak self] _ in
self?.blockUser()
})
present(alert, animated: true)
}
private func blockUser() {
guard let uid = user?.uid else { return }
CometChat.blockUsers([uid]) { [weak self] blockedUsers in
DispatchQueue.main.async {
self?.isBlocked = true
self?.updateBlockButton()
self?.updateStatusLabel(user: self?.user ?? User())
// Emit event for other components
if let user = self?.user {
CometChatUserEvents.emitOnUserBlock(user: user)
}
}
} onError: { [weak self] error in
self?.showAlert(title: "Error", message: error?.errorDescription ?? "Failed to block user")
}
}
private func unblockUser() {
guard let uid = user?.uid else { return }
CometChat.unblockUsers([uid]) { [weak self] unblockedUsers in
DispatchQueue.main.async {
self?.isBlocked = false
self?.updateBlockButton()
self?.updateStatusLabel(user: self?.user ?? User())
// Emit event for other components
if let user = self?.user {
CometChatUserEvents.emitOnUserUnblock(user: user)
}
}
} onError: { [weak self] error in
self?.showAlert(title: "Error", message: error?.errorDescription ?? "Failed to unblock user")
}
}
@objc private func deleteConversation() {
let alert = UIAlertController(
title: "Delete Conversation",
message: "Are you sure you want to delete this conversation? This action cannot be undone.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
self?.performDeleteConversation()
})
present(alert, animated: true)
}
private func performDeleteConversation() {
guard let uid = user?.uid else { return }
CometChat.deleteConversation(conversationWith: uid, conversationType: .user) { [weak self] _ in
DispatchQueue.main.async {
self?.showAlert(title: "Deleted", message: "Conversation has been deleted")
}
} onError: { [weak self] error in
self?.showAlert(title: "Error", message: error?.errorDescription ?? "Failed to delete conversation")
}
}
private func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
// MARK: - Event Listeners
private func addEventListeners() {
CometChat.addUserListener(listenerID, self)
CometChatUserEvents.addListener(listenerID, self)
}
private func removeEventListeners() {
CometChat.removeUserListener(listenerID)
CometChatUserEvents.removeListener(listenerID)
}
}
// MARK: - CometChatUserDelegate
extension ProductionUserDetailsViewController: CometChatUserDelegate {
func onUserOnline(user: User) {
guard user.uid == self.user?.uid else { return }
DispatchQueue.main.async {
self.user = user
self.updateStatusLabel(user: user)
}
}
func onUserOffline(user: User) {
guard user.uid == self.user?.uid else { return }
DispatchQueue.main.async {
self.user = user
self.updateStatusLabel(user: user)
}
}
}
// MARK: - CometChatUserEventListener
extension ProductionUserDetailsViewController: CometChatUserEventListener {
func ccUserBlocked(user: User) {
guard user.uid == self.user?.uid else { return }
DispatchQueue.main.async {
self.isBlocked = true
self.updateBlockButton()
self.updateStatusLabel(user: user)
}
}
func ccUserUnblocked(user: User) {
guard user.uid == self.user?.uid else { return }
DispatchQueue.main.async {
self.isBlocked = false
self.updateBlockButton()
self.updateStatusLabel(user: user)
}
}
}
Launching User Details
Open the user details screen from your app:Report incorrect code
Copy
Ask AI
import UIKit
import CometChatSDK
class UsersListViewController: UIViewController {
func showUserDetails(for user: User) {
let detailsVC = ProductionUserDetailsViewController(user: user)
navigationController?.pushViewController(detailsVC, animated: true)
}
// From a users list
func openFromUsersList() {
let users = CometChatUsers()
users.set(onItemClick: { [weak self] user, _ in
self?.showUserDetails(for: user)
})
navigationController?.pushViewController(users, animated: true)
}
// From a conversation
func openFromConversation(_ conversation: Conversation) {
if let user = conversation.conversationWith as? User {
showUserDetails(for: user)
}
}
}
Block/Unblock API Reference
Block Users
Report incorrect code
Copy
Ask AI
// Block single user
CometChat.blockUsers(["user-uid"]) { blockedUsers in
print("Blocked \(blockedUsers?.count ?? 0) users")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
// Block multiple users
CometChat.blockUsers(["user1", "user2", "user3"]) { blockedUsers in
print("Blocked users: \(blockedUsers?.map { $0.uid ?? "" } ?? [])")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
Unblock Users
Report incorrect code
Copy
Ask AI
// Unblock single user
CometChat.unblockUsers(["user-uid"]) { unblockedUsers in
print("Unblocked \(unblockedUsers?.count ?? 0) users")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
// Unblock multiple users
CometChat.unblockUsers(["user1", "user2"]) { unblockedUsers in
print("Unblocked users: \(unblockedUsers?.map { $0.uid ?? "" } ?? [])")
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
Fetch Blocked Users
Report incorrect code
Copy
Ask AI
let request = BlockedUserRequest.BlockedUserRequestBuilder()
.set(limit: 50)
.set(direction: .blockedByMe) // or .hasBlockedMe, .both
.build()
request.fetchNext { blockedUsers in
for user in blockedUsers ?? [] {
print("Blocked: \(user.name ?? "")")
}
} onError: { error in
print("Error: \(error?.errorDescription ?? "")")
}
Styling
Customize the avatar appearance:Report incorrect code
Copy
Ask AI
let avatarStyle = AvatarStyle()
avatarStyle.cornerRadius = CometChatCornerStyle(cornerRadius: 50)
avatarStyle.borderWidth = 2
avatarStyle.borderColor = .systemBlue
avatarStyle.backgroundColor = .systemGray5
avatar.set(style: avatarStyle)
Components Reference
| Component | Purpose |
|---|---|
CometChatAvatar | Display user profile picture |
CometChatMessages | Open chat interface |
CometChat.blockUsers() | Block users |
CometChat.unblockUsers() | Unblock users |
CometChat.deleteConversation() | Delete chat history |
CometChat.initiateCall() | Start audio/video calls |
Edge Cases
- Self-blocking: Hide block controls when viewing your own profile
- Already blocked: Check block status before showing chat/call options
- Call SDK unavailable: Conditionally show call buttons with
#if canImport(CometChatCallsSDK) - Missing user data: Show placeholder UI for incomplete profiles
Error Handling
| Error | Solution |
|---|---|
| Block failed | Show retry option, check network |
| Unblock failed | Verify user was previously blocked |
| Call initiation failed | Check permissions and call SDK setup |
| Delete conversation failed | Verify conversation exists |
Troubleshooting
| Issue | Solution |
|---|---|
| Status not updating | Verify event listeners are added |
| Block state incorrect | Fetch blocked users list on load |
| Call buttons missing | Import CometChatCallsSDK |
| Avatar not loading | Check user has valid avatar URL |
Related Components
- Users - Display user list
- Conversations - Display conversation list
- Message List - Message display component