Skip to content
Developers Docs

Picture-in-Picture (PiP) Map Mode

Picture-in-Picture lets the map shrink to a compact, glanceable panel — showing just the map canvas, floor selector, compass, and locate-me button — while the user does something else in your app, then expand back to the full experience. It is designed for the route and navigation flow: keep a small live map visible while the rest of the screen shows your own content.

Availability

New in iOS SDK 26.7.1. The PiP API is on MapstedMapUiViewController (import MapstedMapUi). PiP is opt-in — it is completely inactive until you enable it.

Concepts: enabled vs allowed

Two independent conditions govern PiP:

  • Enabled — you turn the feature on with setPipMode(enable:). Until you do, every PiP call is a no-op and the SDK behaves exactly as if PiP did not exist. Check with isPipModeEnabled().
  • Allowed — whether the SDK will accept a collapse right now. PiP collapse is only meaningful during route preview or active navigation, so isPipModeAllowed() reflects that state. Expanding is always allowed.

The mode itself is MapstedPipMode:

Case Meaning
.collapsed Compact map — only the map canvas, floor selector, compass, and locate-me are shown; all panels are suppressed.
.expanded Full map — panels are reconstructed from the current SDK state (active route, selected entity, …).

Enabling PiP and observing changes

Enable PiP, register a delegate to hear about mode changes, and switch modes with applyPipMode(_:). Mode changes are delivered on the main thread.

Android: Picture-in-Picture map mode is an iOS-only feature in 26.7.1 and is not available on Android.

import UIKit
import MapstedMapUi

final class MapHostViewController: UIViewController, MapstedPipModeDelegate {

    // MapstedMapUiViewController.shared is typed UIViewController?; cast it.
    private var mapVC: MapstedMapUiViewController? {
        MapstedMapUiViewController.shared as? MapstedMapUiViewController
    }

    func enablePiP() {
        guard let mapVC = mapVC else { return }
        mapVC.setPipMode(enable: true)     // opt in
        mapVC.addPipModeDelegate(self)     // observe mode changes
    }

    func collapse() {
        guard let mapVC = mapVC, mapVC.isPipModeAllowed() else { return }
        mapVC.applyPipMode(.collapsed)
    }

    func expand() {
        mapVC?.applyPipMode(.expanded)     // always allowed
    }

    func disablePiP() {
        guard let mapVC = mapVC else { return }
        mapVC.removePipModeDelegate(self)
        mapVC.setPipMode(enable: false)    // resets any active PiP state to .expanded
    }

    // MARK: - MapstedPipModeDelegate

    func mapstedPipModeDidChange(to mode: MapstedPipMode) {
        switch mode {
        case .collapsed:
            // Resize your container to the compact height.
            break
        case .expanded:
            // Restore your container to full size.
            break
        @unknown default:
            break
        }
    }

    // Optional — a mode change was rejected (for example, collapse when not allowed).
    func mapstedPipModeDidFail(reason: String) {
        print("PiP change rejected: \(reason)")
    }
}

mapstedPipModeDidChange(to:) is the one method you must implement; mapstedPipModeDidFail(reason:) is optional (it has a default empty implementation) — implement it if you want to tell the user why a collapse was refused.

Typical flow

  1. Call setPipMode(enable: true) once, when your map screen appears.
  2. Register your delegate with addPipModeDelegate(_:).
  3. Start a route or navigation, then call applyPipMode(.collapsed) — guard it with isPipModeAllowed().
  4. In mapstedPipModeDidChange(to:), resize the container that hosts the map.
  5. Call applyPipMode(.expanded) to return to the full map (always succeeds).
  6. When you leave the screen, removePipModeDelegate(_:) and setPipMode(enable: false).

Tips & Considerations

  • PiP is opt-in. Nothing happens until setPipMode(enable: true). isPipModeEnabled() returns false by default.
  • Collapse can be refused. If isPipModeAllowed() is false, applyPipMode(.collapsed) does not collapse and your delegate's mapstedPipModeDidFail(reason:) is called. Expanding always works.
  • Disabling resets state. setPipMode(enable: false) restores panels, zoom, and sets the mode back to .expanded.
  • Delegate callbacks are on the main thread — you can update UI directly.
  • MapstedMapUiViewController.shared is typed UIViewController? — cast with as? MapstedMapUiViewController (shown above).