> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Control Panel

> CometChat Calling SDK v5 - Custom Control Panel for iOS

Build a fully customized control panel for your call interface by hiding the default controls and implementing your own UI with call actions. This guide walks you through creating a custom control panel with essential call controls.

## 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

This guide demonstrates building a basic custom control panel with:

* 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](/calls/ios/join-session))
* Familiarity with [Actions](/calls/ios/actions) and [Events](/calls/ios/events)

***

## Step 1: Hide Default Controls

Configure your session settings to hide the default control panel:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    let sessionSettings = CometChatCalls.sessionSettingsBuilder
        .hideControlPanel(true)
        .build()
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
        hideControlPanel:YES]
        build];
    ```
  </Tab>
</Tabs>

<Note>
  You can also hide individual buttons while keeping the control panel visible. See [SessionSettingsBuilder](/calls/ios/session-settings) for all options.
</Note>

***

## Step 2: Create Custom Layout

Create a custom view for your controls programmatically or in Interface Builder:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    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)
            ])
        }
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    @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
    ```
  </Tab>
</Tabs>

***

## Step 3: Implement Control Actions

Set up button actions and call the appropriate SDK methods:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    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)
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    @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];
    }
    ```
  </Tab>
</Tabs>

***

## Step 4: Handle State Updates

Use `MediaEventsListener` to keep your UI synchronized with the actual call state:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    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) {}
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    @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];
        });
    }
    ```
  </Tab>
</Tabs>

Use `SessionStatusListener` to handle session end events:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    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() {}
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    - (void)onSessionLeft {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
    }

    - (void)onConnectionClosed {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
    }
    ```
  </Tab>
</Tabs>
