Multi Floor View & Stacked Maps
Using Mappedin SDK for iOS with your own map requires a Pro license. Try a demo map for free or refer to the Pricing page for more information.
Multi Floor View
Multi Floor View is a feature that displays all floors in a building stacked vertically below the active floor, allowing users to see the building's vertical structure and navigate between floors. The active floor is fully rendered while lower floors appear as semi-transparent footprints.

A complete example demonstrating Multi Floor View can be found in the Mappedin iOS Github repo: MultiFloorViewDemoViewController.swift
Multi Floor View is enabled by default. It can be controlled using the Show3DMapOptions.multiFloorView option when initializing the map view as shown in the code sample below. In this example, the map view is initialized with the initialFloor set to m_952cd353abcb1a13, which is ID of the 10th floor of the building. Other Multi Floor View options are defined in MultiFloorViewOptions and include the ability to set the gap between floors as well as whether the camera should move when the active floor changes.
Code Sample
const mapView = await show3dMap(
document.getElementById("mappedin-map") as HTMLDivElement,
mapData,
{
initialFloor: "m_952cd353abcb1a13",
multiFloorView: {
enabled: true, // Default is true.
floorGap: 10, //Default is 10.
updateCameraElevationOnFloorChange: true, //Default is true.
},
}
);
It is possible to set multiple floors to be visible at once, which can be useful for overlaying data within the building, such as the locations of security cameras or other assets. This can be done by setting the visible property to true for each floor that should be shown, using FloorUpdateState as shown in the code sample below.
// Show the 6th floor as well.
const floor = mapData.getByType("floor").find((f) => f.elevation === 6);
mapView.updateState(floor, {
geometry: { visible: true },
});
When displaying multiple floors, by default content such as labels, markers, paths and images are only visible on the active floor. These can be displayed on additional visible floors by enabling them for the floor using FloorUpdateState as shown below.
mapView.updateState(
floor: floor,
state: FloorUpdateState(
visible: true,
altitude: 0.0,
geometry: FloorUpdateState.Geometry(visible: true),
images: FloorUpdateState.Images(visible: true),
labels: FloorUpdateState.Labels(enabled: true),
markers: FloorUpdateState.Markers(enabled: true),
paths: FloorUpdateState.Paths(visible: true)
)
) { result in
switch result {
case .success:
// Handle success
break
case .failure(let error):
// Handle failure
print("Error: \(error)")
}
}
Stacked Maps
Stacked Maps consist of individual layers that represent different floors of a multi-story building. These layers are conceptually or digitally stacked to create a complete view of the structure, allowing users to visualize, navigate, and interact with multiple floors in an integrated way.

A complete example demonstrating Stacked Maps can be found in the Mappedin iOS Github repo: StackedMapsDemoViewController.swift
Stacked Maps can be enabled by setting the visibility of floors that should appear in the stack to true and altering their altitude defined in FloorUpdateState, so that each floor is raised above the previous floor. There are two ways to modify the visibility and altitude of a floor:
- MapView.animateState() gradually moves the floor to the new altitude over a specified duration of time.
- MapView.updateState() instantly snaps the floor to the new altitude.
Alternative visualization methods can also be created by using variations of this technique. For example, an app may wish to only show the top and bottom floors of a building, or only show the floors that are currently accessible to the user by setting the visibility of the floors to true or false based on the app's requirements.
When displaying multiple floors, by default content such as labels, markers, paths and images are only visible on the active floor. These can be displayed on additional visible floors by enabling them for the floor using FloorUpdateState as shown below.
mapView.updateState(
floor: floor,
state: FloorUpdateState(
images: FloorUpdateState.Images(visible: true),
labels: FloorUpdateState.Labels(enabled: true),
markers: FloorUpdateState.Markers(enabled: true),
paths: FloorUpdateState.Paths(visible: true)
)
) { result in
switch result {
case .success:
// Handle success
break
case .failure(let error):
// Handle failure
print("Error: \(error)")
}
}
When using Stacked Maps with Dynamic Focus (coming soon) or Navigation, the MapView.manualFloorVisibility property should be set to true to ensure that the floors remain visible. Otherwise they could be hidden by the Dynamic Focus or Navigation features, which by default will hide all floors that are not the active floor.
Stacked Maps Utility Class
The following utility class can be used to expand and collapse the stacked maps view using the animateState or updateState methods.
import Foundation
import Mappedin
/// Options for expanding floors in a stacked view.
public struct ExpandOptions {
/// The vertical spacing between floors in meters. Default: 10
public let distanceBetweenFloors: Double
/// Whether to animate the floor expansion. Default: true
public let animate: Bool
/// The camera pan mode to use ("default" or "elevation"). Default: "elevation"
public let cameraPanMode: String
public init(
distanceBetweenFloors: Double = 10.0,
animate: Bool = true,
cameraPanMode: String = "elevation"
) {
self.distanceBetweenFloors = distanceBetweenFloors
self.animate = animate
self.cameraPanMode = cameraPanMode
}
}
/// Options for collapsing floors back to their original positions.
public struct CollapseOptions {
/// Whether to animate the floor collapse. Default: true
public let animate: Bool
public init(animate: Bool = true) {
self.animate = animate
}
}
/// Utility class for managing stacked floor views.
///
/// Provides functions to expand all floors vertically (stacked view) and collapse them back
/// to a single floor view. This creates a 3D exploded view effect where all floors are visible
/// at different altitudes.
///
/// Example usage:
/// ```swift
/// // Expand floors with default options
/// StackedMapsUtils.expandFloors(mapView: mapView)
///
/// // Expand floors with custom gap
/// StackedMapsUtils.expandFloors(mapView: mapView, options: ExpandOptions(distanceBetweenFloors: 20.0))
///
/// // Collapse floors back
/// StackedMapsUtils.collapseFloors(mapView: mapView)
/// ```
public class StackedMapsUtils {
/// Expands all floors vertically to create a stacked view.
///
/// Each floor is positioned at an altitude based on its elevation multiplied by the
/// distance between floors. This creates a 3D exploded view where all floors are visible.
///
/// - Parameters:
/// - mapView: The MapView instance
/// - options: Options controlling the expansion behavior
public static func expandFloors(
mapView: MapView,
options: ExpandOptions = ExpandOptions()
) {
// Set camera pan mode to elevation for better navigation in stacked view
mapView.camera.setPanMode(options.cameraPanMode)
// Get the current floor ID to identify the active floor
mapView.currentFloor { currentFloorResult in
let currentFloorId: String?
switch currentFloorResult {
case .success(let floor):
currentFloorId = floor?.id
case .failure:
currentFloorId = nil
}
// Get all floors
mapView.mapData.getByType(MapDataType.floor) { (result: Result<[Floor], Error>) in
switch result {
case .success(let floors):
for floor in floors {
let newAltitude = floor.elevation * options.distanceBetweenFloors
let isCurrentFloor = floor.id == currentFloorId
// First, make sure the floor is visible
mapView.getState(floor: floor) { stateResult in
switch stateResult {
case .success(let currentState):
if let state = currentState,
(!state.visible || !state.geometry.visible) {
// Make the floor visible first with 0 opacity if not current
mapView.updateState(
floor: floor,
state: FloorUpdateState(
altitude: 0.0,
visible: true,
geometry: FloorUpdateState.Geometry(
opacity: isCurrentFloor ? 1.0 : 0.0,
visible: true
)
)
)
}
// Then animate or update to the new altitude
if options.animate {
mapView.animateState(
floor: floor,
state: FloorUpdateState(
altitude: newAltitude,
geometry: FloorUpdateState.Geometry(
opacity: 1.0
)
)
)
} else {
mapView.updateState(
floor: floor,
state: FloorUpdateState(
altitude: newAltitude,
visible: true,
geometry: FloorUpdateState.Geometry(
opacity: 1.0,
visible: true
)
)
)
}
case .failure:
break
}
}
}
case .failure:
break
}
}
}
}
/// Collapses all floors back to their original positions.
///
/// Floors are returned to altitude 0, and only the current floor remains fully visible.
/// Other floors are hidden to restore the standard single-floor view.
///
/// - Parameters:
/// - mapView: The MapView instance
/// - options: Options controlling the collapse behavior
public static func collapseFloors(
mapView: MapView,
options: CollapseOptions = CollapseOptions()
) {
// Reset camera pan mode to default
mapView.camera.setPanMode("default")
// Get the current floor ID to identify the active floor
mapView.currentFloor { currentFloorResult in
let currentFloorId: String?
switch currentFloorResult {
case .success(let floor):
currentFloorId = floor?.id
case .failure:
currentFloorId = nil
}
// Get all floors
mapView.mapData.getByType(MapDataType.floor) { (result: Result<[Floor], Error>) in
switch result {
case .success(let floors):
for floor in floors {
let isCurrentFloor = floor.id == currentFloorId
if options.animate {
// Animate to altitude 0 and fade out non-current floors
mapView.animateState(
floor: floor,
state: FloorUpdateState(
altitude: 0.0,
geometry: FloorUpdateState.Geometry(
opacity: isCurrentFloor ? 1.0 : 0.0
)
)
)
// After animation, hide non-current floors
if !isCurrentFloor {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
mapView.updateState(
floor: floor,
state: FloorUpdateState(
altitude: 0.0,
visible: false,
geometry: FloorUpdateState.Geometry(
opacity: 0.0,
visible: false
)
)
)
}
}
} else {
mapView.updateState(
floor: floor,
state: FloorUpdateState(
altitude: 0.0,
visible: isCurrentFloor,
geometry: FloorUpdateState.Geometry(
opacity: isCurrentFloor ? 1.0 : 0.0,
visible: isCurrentFloor
)
)
)
}
}
case .failure:
break
}
}
}
}
}
A complete example demonstrating Stacked Maps can be found in the Mappedin iOS Github repo: StackedMapsDemoViewController.swift