> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/ios/maps/llms.txt)

# Indoor mapping

The **Mapbox Maps SDK for iOS** provides indoor mapping support, allowing your application to display detailed floor plans inside buildings and let users switch between floors. Indoor mapping is part of the [Mapbox Standard](https://docs.mapbox.com/map-styles/standard/guides/) style and is controlled via a style configuration property.

![](https://docs.mapbox.com/ios/assets/ideal-img/maps-guides-indoor-overview.cac0cb2.480.png)

> **Note (warning): Experimental API**
> 
> Indoor mapping is an experimental feature. APIs may change in future releases. You must import with `@_spi(Experimental) import MapboxMaps`.

## Enable indoor mapping

Indoor mapping is included in the Mapbox Standard style, but the feature is **disabled by default**. To enable it, set the `showIndoor` configuration property on the `basemap` style import to `true`:

```swift
try? mapView.mapboxMap.setStyleImportConfigProperty(
    for: "basemap",
    config: "showIndoor",
    value: true
)
```

or using predefined MapStyle type wrapper that allows to set configuration values directly:

```swift
private var mapStyle: MapStyle {
    .standard(
        lightPreset: lightPreset,
        showPointOfInterestLabels: labelsSetting,
        showIndoor: true
    )
}
```

Once enabled, buildings that have indoor map data will render their indoor floor plans at appropriate zoom levels.

## Indoor selector ornament

The indoor selector ornament provides a floor selection UI that lets users browse and switch between floors in an indoor-mapped building.

### Setup

To show the indoor selector, set its visibility on the `MapView` ornaments:

```swift
@_spi(Experimental) import MapboxMaps

// Show the indoor floor selector
mapView.ornaments.options.indoorSelector.visibility = .visible

// Optionally adjust margins to avoid overlapping with other controls
mapView.ornaments.options.indoorSelector.margins = CGPoint(x: 8.0, y: 56.0)
```

When indoor data becomes available (e.g., the user pans to a building with indoor mapping), the selector automatically populates with available floors and lets the user tap to switch floors.

## Observing indoor state

Use the `indoor` property on `MapboxMap` to subscribe to changes in the indoor state. The `onIndoorUpdated` signal fires whenever the set of available floors changes or when a floor is selected:

> **Note (warning): Experimental API**
> 
> The programmatic indoor API is experimental and subject to change. In future releases, this API may be extended to allow full customization of the indoor control, including building custom floor selector UI.

```swift
@_spi(Experimental) import MapboxMaps

private var cancellables = Set<AnyCancelable>()

mapView.mapboxMap.indoor.onIndoorUpdated.sink { indoorState in
    print("Floors: \(indoorState.floors.map { $0.id })")
    print("Selected floor id: \(indoorState.selectedFloorId ?? "none")")
}.store(in: &cancellables)
```

This is useful for building custom floor selection user interfaces or responding to floor changes in your application logic.

## Disable indoor mapping

To completely disable indoor mapping after it was enabled, you should both turn off the style configuration and hide the indoor selector ornament.

### Disable style configuration

Set the `showIndoor` configuration property to `false`:

```swift
try? mapView.mapboxMap.setStyleImportConfigProperty(
    for: "basemap",
    config: "showIndoor",
    value: false
)
```

### Hide indoor selector ornament

Set the `visibility` of the indoor selector to `.hidden`:

```swift
mapView.ornaments.options.indoorSelector.visibility = .hidden
```

## Complete example

Here's a complete example showing how to set up indoor mapping with the floor selector in a UIKit view controller:

```swift
import UIKit
@_spi(Experimental) import MapboxMaps

final class IndoorViewController: UIViewController {
    private var mapView: MapView!
    private var cancellables = Set<AnyCancelable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        let cameraOptions = CameraOptions(
            center: CLLocationCoordinate2D(latitude: 35.5483, longitude: 139.7780),
            zoom: 16,
            bearing: 12,
            pitch: 60
        )

        let options = MapInitOptions(cameraOptions: cameraOptions)
        mapView = MapView(frame: view.bounds, mapInitOptions: options)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(mapView)

        // Enable indoor in the Standard style
        try? mapView.mapboxMap.setStyleImportConfigProperty(
            for: "basemap",
            config: "showIndoor",
            value: true
        )

        // Show the indoor floor selector ornament
        mapView.ornaments.options.indoorSelector.visibility = .visible
        mapView.ornaments.options.indoorSelector.margins = CGPoint(x: 8.0, y: 56.0)

        // Show scale bar
        mapView.ornaments.options.scaleBar.visibility = .visible

        // Observe indoor state changes
        mapView.mapboxMap.indoor.onIndoorUpdated.sink { indoorState in
            print("Selected floor id: \(indoorState.selectedFloorId ?? "none")")
        }.store(in: &cancellables)
    }
}
```

### SwiftUI

If you are using SwiftUI, you can configure indoor mapping using the `MapReader` and style configuration modifiers:

```swift
import SwiftUI
@_spi(Experimental) import MapboxMaps

struct IndoorMapView: View {
    var body: some View {
        Map(initialViewport: .camera(
            center: CLLocationCoordinate2D(latitude: 35.5483, longitude: 139.7780),
            zoom: 16,
            bearing: 12,
            pitch: 60
        ))
        .mapStyle(.standard)
        .ornamentOptions(OrnamentOptions(
            indoorSelector: OrnamentVisibilityOptions(visibility: .visible)
        ))
        .onStyleLoaded { _ in
            // Enable indoor mapping via style config
        }
    }
}
```