> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-ios-v5-calls-corrections.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Picture-in-Picture

> Configure picture-in-picture for CometChat Calls SDK v5 on iOS to keep video calls visible while users multitask.

Enable Picture-in-Picture (PiP) mode to allow users to continue their call in a floating window while using other apps. PiP provides a seamless multitasking experience during calls.

<Warning>
  **`enablePictureInPictureLayout()` alone does nothing visible.** It only tells the call UI to
  re-lay-out for a smaller window — it does not create, move or float a window. Something has to
  provide the small window, and there are two different ways to do that. Pick one before you write
  any code.
</Warning>

## Two kinds of Picture-in-Picture

|                             | In-app PiP                                                 | System PiP                                                                                                                   |
| --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **What the user sees**      | A small draggable call tile floating over **your own app** | A floating window over **other apps** and the Home Screen                                                                    |
| **Who provides the window** | **`PiPViewCoordinator`, shipped in the Calls SDK**         | Apple's AVKit — **also implemented inside the Calls SDK**, behind an opt-in flag                                             |
| **Setup**                   | A few lines — see below                                    | `SessionSettingsBuilder.enableIOSPictureInPicture(true)` + Background Modes; or wire `AVPictureInPictureController` yourself |
| **Availability**            | Any supported iOS version                                  | iPhone iOS 15+, iPad iOS 9+                                                                                                  |

In both cases you still call `enablePictureInPictureLayout()` / `disablePictureInPictureLayout()`
so the SDK reshapes the call UI to match.

<Note>
  **System PiP is implemented inside the Calls SDK**, not only in Apple's API: the 5.0.4 binary links
  `AVKit` and carries the whole path (`AVPictureInPictureController`,
  `AVPictureInPictureControllerContentSource`, `AVPictureInPictureVideoCallViewController`), gated
  behind `SessionSettingsBuilder.enableIOSPictureInPicture(_:)`. Enable that flag and add the
  **Background Modes → Audio, AirPlay, and Picture in Picture** capability. You can still host
  `AVPictureInPictureController` yourself if you want full control over the window.
</Note>

## In-app PiP with `PiPViewCoordinator`

`PiPViewCoordinator` ships with the Calls SDK. Give it the same `UIView` you passed to
`joinSession(container:)` and it turns that view into a draggable, tappable mini-call.

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    import CometChatCallsSDK

    final class PiPCallViewController: UIViewController, PiPViewCoordinatorDelegate {

        private let callContainer = UIView()
        private var pipCoordinator: PiPViewCoordinator?

        override func viewDidLoad() {
            super.viewDidLoad()

            // The coordinator manages the SAME view the call is rendered into.
            let coordinator = PiPViewCoordinator(withView: callContainer)
            coordinator.delegate = self
            coordinator.initialPositionInSuperView = .lowerRightCorner
            coordinator.dragBoundInsets = UIEdgeInsets(top: 25, left: 8, bottom: 8, right: 8)

            // Pass the parent explicitly — with no argument it falls back to the key window.
            coordinator.configureAsStickyView(withParentView: view)
            coordinator.show()
            pipCoordinator = coordinator

            // ...then join the session into `callContainer` as usual.
        }

        /// Shrink to the floating tile.
        func enterPiP() {
            pipCoordinator?.enterPictureInPicture()   // resize + enable dragging + exit button
            CallSession.shared.enablePictureInPictureLayout()   // reshape the call UI to match
        }

        /// Called by the coordinator AFTER it has already restored the full-size view —
        /// this is a notification that PiP ended, not a request to end it.
        func exitPictureInPicture() {
            CallSession.shared.disablePictureInPictureLayout()
        }

        /// Keep the tile inside the screen on rotation or a size change.
        override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
            coordinator.animate { [weak self] _ in
                guard let self else { return }
                self.pipCoordinator?.resetBounds(bounds: CGRect(origin: .zero, size: size))
            }
        }
    }
    ```
  </Tab>
</Tabs>

### `PiPViewCoordinator` reference

| Member                                    | Purpose                                                                                                                        |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `init(withView:)`                         | The view the call renders into — the same one given to `joinSession(container:)`                                               |
| `configureAsStickyView(withParentView:)`  | Adds the view above everything in the parent. **Pass the parent explicitly**; with no argument it falls back to the key window |
| `show(completion:)` / `hide(completion:)` | Fade in / out. `completion` is `AnimationCompletion` = `(Bool) -> Void`                                                        |
| `enterPictureInPicture()`                 | Shrinks the view, enables the drag gesture, and adds a tap-to-reveal exit button                                               |
| `exitPictureInPicture()`                  | Restores full size, removes the drag gesture and exit button, then calls the delegate                                          |
| `resetBounds(bounds:)`                    | Call on rotation or any size change                                                                                            |
| `stopDragGesture()`                       | Pin the tile in place                                                                                                          |
| `configureExitPiPButton(target:action:)`  | Supply your own exit button                                                                                                    |
| `initialPositionInSuperView`              | `.lowerRightCorner` (default) · `.upperRightCorner` · `.lowerLeftCorner` · `.upperLeftCorner`                                  |
| `dragBoundInsets`                         | How far the tile may be dragged toward each edge                                                                               |
| `delegate`                                | `PiPViewCoordinatorDelegate` — one method, `exitPictureInPicture()`                                                            |

<Note>
  The tile size is fixed at 150px. The `c` property that used to set it is deprecated and ignored.
</Note>

## How the SDK layout hook works

1. Something provides the small window — `PiPViewCoordinator` (in-app) or `AVPictureInPictureController` (system)
2. You notify the Calls SDK by calling `enablePictureInPictureLayout()`
3. The SDK adjusts the call UI to fit the smaller window (hides controls, optimizes layout)
4. When exiting, call `disablePictureInPictureLayout()` to restore the full UI

## Enable Picture-in-Picture

Enter PiP mode programmatically using the `enablePictureInPictureLayout()` action:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CallSession.shared.enablePictureInPictureLayout()
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    [[CallSession shared] enablePictureInPictureLayout];
    ```
  </Tab>
</Tabs>

## Disable Picture-in-Picture

Exit PiP mode and return to the full-screen call interface:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CallSession.shared.disablePictureInPictureLayout()
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    [[CallSession shared] disablePictureInPictureLayout];
    ```
  </Tab>
</Tabs>

## Listen for PiP Events

Monitor PiP mode transitions using `LayoutListener` to update your UI accordingly:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    class CallViewController: UIViewController, LayoutListener {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            CallSession.shared.addLayoutListener(self)
        }
        
        deinit {
            CallSession.shared.removeLayoutListener(self)
        }
        
        func onPictureInPictureLayoutEnabled() {
            print("Entered PiP mode")
            // Hide custom overlays or controls
            hideCustomControls()
        }

        func onPictureInPictureLayoutDisabled() {
            print("Exited PiP mode")
            // Show custom overlays or controls
            showCustomControls()
        }

        func onCallLayoutChanged(layoutType: LayoutType) {}
        func onParticipantListVisible() {}
        func onParticipantListHidden() {}
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    @interface CallViewController () <LayoutListener>
    @end

    @implementation CallViewController

    - (void)viewDidLoad {
        [super viewDidLoad];
        [[CallSession shared] addLayoutListener:self];
    }

    - (void)dealloc {
        [[CallSession shared] removeLayoutListener:self];
    }

    - (void)onPictureInPictureLayoutEnabled {
        NSLog(@"Entered PiP mode");
        // Hide custom overlays or controls
        [self hideCustomControls];
    }

    - (void)onPictureInPictureLayoutDisabled {
        NSLog(@"Exited PiP mode");
        // Show custom overlays or controls
        [self showCustomControls];
    }

    - (void)onCallLayoutChangedWithLayoutType:(LayoutType)layoutType {}
    - (void)onParticipantListVisible {}
    - (void)onParticipantListHidden {}

    @end
    ```
  </Tab>
</Tabs>

## System PiP setup (floating over other apps)

Only needed for System PiP — In-app PiP via `PiPViewCoordinator` requires none of this.

**1. Enable Background Modes**

In your project's **Signing & Capabilities**, add the **Background Modes** capability and enable:

* Audio, AirPlay, and Picture in Picture

**2. Configure AVAudioSession**

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    import AVFoundation

    func configureAudioSession() {
        do {
            try AVAudioSession.sharedInstance().setCategory(
                .playAndRecord,
                mode: .videoChat,
                options: [.allowBluetooth, .defaultToSpeaker]
            )
            try AVAudioSession.sharedInstance().setActive(true)
        } catch {
            print("Failed to configure audio session: \(error)")
        }
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objectivec theme={null}
    #import <AVFoundation/AVFoundation.h>

    - (void)configureAudioSession {
        NSError *error = nil;
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
                                                mode:AVAudioSessionModeVideoChat
                                             options:AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionDefaultToSpeaker
                                               error:&error];
        [[AVAudioSession sharedInstance] setActive:YES error:&error];
    }
    ```
  </Tab>
</Tabs>

<Note>
  PiP mode is available on iOS 14.0 and later for iPhones, and iOS 9.0 and later for iPads.
</Note>
