Overview
Custom control panels allow you to:- Match your app’s branding and design language
- Simplify the interface by showing only relevant controls
- Add custom functionality and workflows
- Create unique user experiences
- Mute/Unmute audio button
- Pause/Resume video button
- Switch camera button
- End call button
Prerequisites
- CometChat Calls SDK installed and initialized
- Active call session (see Join Session)
- Familiarity with Actions and Events
Step 1: Hide Default Controls
Configure your session settings to hide the default control panel:- Swift
- Objective-C
Report incorrect code
Copy
Ask AI
let sessionSettings = CometChatCalls.sessionSettingsBuilder
.hideControlPanel(true)
.build()
Report incorrect code
Copy
Ask AI
SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
hideControlPanel:YES]
build];
You can also hide individual buttons while keeping the control panel visible. See SessionSettingsBuilder for all options.
Step 2: Create Custom Layout
Create a custom view for your controls programmatically or in Interface Builder:- Swift
- Objective-C
Report incorrect code
Copy
Ask AI
class CallViewController: UIViewController {
// Call container view
private let callContainer = UIView()
// Custom control panel
private let controlPanel = UIStackView()
private let btnToggleAudio = UIButton(type: .system)
private let btnToggleVideo = UIButton(type: .system)
private let btnSwitchCamera = UIButton(type: .system)
private let btnEndCall = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupControlListeners()
}
private func setupUI() {
view.backgroundColor = .black
// Setup call container
callContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(callContainer)
// Setup control panel
controlPanel.axis = .horizontal
controlPanel.distribution = .equalSpacing
controlPanel.alignment = .center
controlPanel.spacing = 20
controlPanel.translatesAutoresizingMaskIntoConstraints = false
controlPanel.backgroundColor = UIColor.black.withAlphaComponent(0.8)
controlPanel.layoutMargins = UIEdgeInsets(top: 16, left: 32, bottom: 16, right: 32)
controlPanel.isLayoutMarginsRelativeArrangement = true
view.addSubview(controlPanel)
// Configure buttons
configureButton(btnToggleAudio, imageName: "mic.fill", backgroundColor: .darkGray)
configureButton(btnToggleVideo, imageName: "video.fill", backgroundColor: .darkGray)
configureButton(btnSwitchCamera, imageName: "camera.rotate.fill", backgroundColor: .darkGray)
configureButton(btnEndCall, imageName: "phone.down.fill", backgroundColor: .systemRed)
// Add buttons to control panel
controlPanel.addArrangedSubview(btnToggleAudio)
controlPanel.addArrangedSubview(btnToggleVideo)
controlPanel.addArrangedSubview(btnSwitchCamera)
controlPanel.addArrangedSubview(btnEndCall)
// Layout constraints
NSLayoutConstraint.activate([
callContainer.topAnchor.constraint(equalTo: view.topAnchor),
callContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
callContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
callContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
controlPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controlPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
controlPanel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
controlPanel.heightAnchor.constraint(equalToConstant: 80)
])
}
private func configureButton(_ button: UIButton, imageName: String, backgroundColor: UIColor) {
button.setImage(UIImage(systemName: imageName), for: .normal)
button.tintColor = .white
button.backgroundColor = backgroundColor
button.layer.cornerRadius = 28
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 56),
button.heightAnchor.constraint(equalToConstant: 56)
])
}
}
Report incorrect code
Copy
Ask AI
@interface CallViewController ()
@property (nonatomic, strong) UIView *callContainer;
@property (nonatomic, strong) UIStackView *controlPanel;
@property (nonatomic, strong) UIButton *btnToggleAudio;
@property (nonatomic, strong) UIButton *btnToggleVideo;
@property (nonatomic, strong) UIButton *btnSwitchCamera;
@property (nonatomic, strong) UIButton *btnEndCall;
@end
@implementation CallViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self setupControlListeners];
}
- (void)setupUI {
self.view.backgroundColor = [UIColor blackColor];
// Setup call container
self.callContainer = [[UIView alloc] init];
self.callContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.callContainer];
// Setup control panel
self.controlPanel = [[UIStackView alloc] init];
self.controlPanel.axis = UILayoutConstraintAxisHorizontal;
self.controlPanel.distribution = UIStackViewDistributionEqualSpacing;
self.controlPanel.alignment = UIStackViewAlignmentCenter;
self.controlPanel.spacing = 20;
self.controlPanel.translatesAutoresizingMaskIntoConstraints = NO;
self.controlPanel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
[self.view addSubview:self.controlPanel];
// Configure and add buttons
self.btnToggleAudio = [self createButtonWithImageName:@"mic.fill" backgroundColor:[UIColor darkGrayColor]];
self.btnToggleVideo = [self createButtonWithImageName:@"video.fill" backgroundColor:[UIColor darkGrayColor]];
self.btnSwitchCamera = [self createButtonWithImageName:@"camera.rotate.fill" backgroundColor:[UIColor darkGrayColor]];
self.btnEndCall = [self createButtonWithImageName:@"phone.down.fill" backgroundColor:[UIColor systemRedColor]];
[self.controlPanel addArrangedSubview:self.btnToggleAudio];
[self.controlPanel addArrangedSubview:self.btnToggleVideo];
[self.controlPanel addArrangedSubview:self.btnSwitchCamera];
[self.controlPanel addArrangedSubview:self.btnEndCall];
// Layout constraints
[NSLayoutConstraint activateConstraints:@[
[self.callContainer.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.callContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.callContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.callContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.controlPanel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.controlPanel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.controlPanel.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.controlPanel.heightAnchor constraintEqualToConstant:80]
]];
}
- (UIButton *)createButtonWithImageName:(NSString *)imageName backgroundColor:(UIColor *)backgroundColor {
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setImage:[UIImage systemImageNamed:imageName] forState:UIControlStateNormal];
button.tintColor = [UIColor whiteColor];
button.backgroundColor = backgroundColor;
button.layer.cornerRadius = 28;
button.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[button.widthAnchor constraintEqualToConstant:56],
[button.heightAnchor constraintEqualToConstant:56]
]];
return button;
}
@end
Step 3: Implement Control Actions
Set up button actions and call the appropriate SDK methods:- Swift
- Objective-C
Report incorrect code
Copy
Ask AI
private var isAudioMuted = false
private var isVideoPaused = false
private func setupControlListeners() {
btnToggleAudio.addTarget(self, action: #selector(toggleAudio), for: .touchUpInside)
btnToggleVideo.addTarget(self, action: #selector(toggleVideo), for: .touchUpInside)
btnSwitchCamera.addTarget(self, action: #selector(switchCamera), for: .touchUpInside)
btnEndCall.addTarget(self, action: #selector(endCall), for: .touchUpInside)
}
@objc private func toggleAudio() {
if isAudioMuted {
CallSession.shared.unMuteAudio()
} else {
CallSession.shared.muteAudio()
}
}
@objc private func toggleVideo() {
if isVideoPaused {
CallSession.shared.resumeVideo()
} else {
CallSession.shared.pauseVideo()
}
}
@objc private func switchCamera() {
CallSession.shared.switchCamera()
}
@objc private func endCall() {
CallSession.shared.leaveSession()
navigationController?.popViewController(animated: true)
}
Report incorrect code
Copy
Ask AI
@interface CallViewController ()
@property (nonatomic, assign) BOOL isAudioMuted;
@property (nonatomic, assign) BOOL isVideoPaused;
@end
- (void)setupControlListeners {
[self.btnToggleAudio addTarget:self action:@selector(toggleAudio) forControlEvents:UIControlEventTouchUpInside];
[self.btnToggleVideo addTarget:self action:@selector(toggleVideo) forControlEvents:UIControlEventTouchUpInside];
[self.btnSwitchCamera addTarget:self action:@selector(switchCamera) forControlEvents:UIControlEventTouchUpInside];
[self.btnEndCall addTarget:self action:@selector(endCall) forControlEvents:UIControlEventTouchUpInside];
}
- (void)toggleAudio {
if (self.isAudioMuted) {
[[CallSession shared] unMuteAudio];
} else {
[[CallSession shared] muteAudio];
}
}
- (void)toggleVideo {
if (self.isVideoPaused) {
[[CallSession shared] resumeVideo];
} else {
[[CallSession shared] pauseVideo];
}
}
- (void)switchCamera {
[[CallSession shared] switchCamera];
}
- (void)endCall {
[[CallSession shared] leaveSession];
[self.navigationController popViewControllerAnimated:YES];
}
Step 4: Handle State Updates
UseMediaEventsListener to keep your UI synchronized with the actual call state:
- Swift
- Objective-C
Report incorrect code
Copy
Ask AI
extension CallViewController: MediaEventsListener {
override func viewDidLoad() {
super.viewDidLoad()
// ... other setup
CallSession.shared.addMediaEventsListener(self)
}
deinit {
CallSession.shared.removeMediaEventsListener(self)
}
func onAudioMuted() {
DispatchQueue.main.async {
self.isAudioMuted = true
self.btnToggleAudio.setImage(UIImage(systemName: "mic.slash.fill"), for: .normal)
}
}
func onAudioUnMuted() {
DispatchQueue.main.async {
self.isAudioMuted = false
self.btnToggleAudio.setImage(UIImage(systemName: "mic.fill"), for: .normal)
}
}
func onVideoPaused() {
DispatchQueue.main.async {
self.isVideoPaused = true
self.btnToggleVideo.setImage(UIImage(systemName: "video.slash.fill"), for: .normal)
}
}
func onVideoResumed() {
DispatchQueue.main.async {
self.isVideoPaused = false
self.btnToggleVideo.setImage(UIImage(systemName: "video.fill"), for: .normal)
}
}
// Other MediaEventsListener callbacks
func onRecordingStarted() {}
func onRecordingStopped() {}
func onScreenShareStarted() {}
func onScreenShareStopped() {}
func onAudioModeChanged(audioModeType: AudioModeType) {}
func onCameraFacingChanged(cameraFacing: CameraFacing) {}
}
Report incorrect code
Copy
Ask AI
@interface CallViewController () <MediaEventsListener>
@end
- (void)viewDidLoad {
[super viewDidLoad];
// ... other setup
[[CallSession shared] addMediaEventsListener:self];
}
- (void)dealloc {
[[CallSession shared] removeMediaEventsListener:self];
}
- (void)onAudioMuted {
dispatch_async(dispatch_get_main_queue(), ^{
self.isAudioMuted = YES;
[self.btnToggleAudio setImage:[UIImage systemImageNamed:@"mic.slash.fill"] forState:UIControlStateNormal];
});
}
- (void)onAudioUnMuted {
dispatch_async(dispatch_get_main_queue(), ^{
self.isAudioMuted = NO;
[self.btnToggleAudio setImage:[UIImage systemImageNamed:@"mic.fill"] forState:UIControlStateNormal];
});
}
- (void)onVideoPaused {
dispatch_async(dispatch_get_main_queue(), ^{
self.isVideoPaused = YES;
[self.btnToggleVideo setImage:[UIImage systemImageNamed:@"video.slash.fill"] forState:UIControlStateNormal];
});
}
- (void)onVideoResumed {
dispatch_async(dispatch_get_main_queue(), ^{
self.isVideoPaused = NO;
[self.btnToggleVideo setImage:[UIImage systemImageNamed:@"video.fill"] forState:UIControlStateNormal];
});
}
SessionStatusListener to handle session end events:
- Swift
- Objective-C
Report incorrect code
Copy
Ask AI
extension CallViewController: SessionStatusListener {
func onSessionLeft() {
DispatchQueue.main.async {
self.navigationController?.popViewController(animated: true)
}
}
func onConnectionClosed() {
DispatchQueue.main.async {
self.navigationController?.popViewController(animated: true)
}
}
func onSessionJoined() {}
func onSessionTimedOut() {}
func onConnectionLost() {}
func onConnectionRestored() {}
}
Report incorrect code
Copy
Ask AI
- (void)onSessionLeft {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}
- (void)onConnectionClosed {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}