Skip to main content
Version: 6.0

Using SwiftUI

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.

The Mappedin SDK for iOS v6 uses a MapView class that provides a view property (a UIView). To use it in SwiftUI, wrap it in a UIViewRepresentable. This allows a SwiftUI-based app to display and interact with indoor maps.

tip

A complete example demonstrating SwiftUI can be found in the Mappedin iOS GitHub repo: SwiftUI With Mappedin Project implemented in the MapViewRepresentable.swift file.

Setting Up the MapModel

Mappedin SDK for iOS v6 uses an event-based API. Events like click handlers must be registered after the map finishes loading, and because UIViewRepresentable is a struct that SwiftUI may recreate, the MapView instance must be stored in a stable, long-lived object. An ObservableObject is the idiomatic SwiftUI approach. It holds the MapView and any @Published properties that drive the UI, such as alert state for click events. Since the MapView lives on the model, SDK methods can be called directly from SwiftUI buttons and controls.

import SwiftUI
import Mappedin

class MapModel: ObservableObject {
let mapView = MapView()
@Published var showAlert = false
@Published var alertTitle = ""
@Published var alertMessage = ""
}

Creating the MapViewRepresentable

The MapViewRepresentable struct wraps the SDK's MapView and conforms to UIViewRepresentable. It receives the MapModel via @ObservedObject and accesses the MapView through model.mapView.

Inside makeUIView, the map data is loaded with getMapData using a GetMapDataWithCredentialsOptions object containing an API key, secret, and map ID. Once the data loads successfully, show3dMap is called to render the 3D map.

struct MapViewRepresentable: UIViewRepresentable {
@ObservedObject var model: MapModel

func makeUIView(context: Context) -> UIView {
let options = GetMapDataWithCredentialsOptions(
key: "5eab30aa91b055001a68e996",
secret: "RJyRXKcryCMy4erZqqCbuB1NbR66QTGNXVE0x3Pg6oCIlUR1",
mapId: "mappedin-demo-mall"
)

model.mapView.getMapData(options: options) { result in
if case .success = result {
model.mapView.show3dMap(options: Show3DMapOptions()) { showResult in
if case .success = showResult {
onMapReady()
} else if case .failure(let error) = showResult {
print("show3dMap error: \(error)")
}
}
} else if case .failure(let error) = result {
print("getMapData error: \(error)")
}
}

return model.mapView.view
}

func updateUIView(_ uiView: UIView, context: Context) {}
}

The GetMapDataWithCredentialsOptions object contains a key, secret, and mapId. To get started, use the Mappedin demo API key and secret. To use your own venues you will need your own unique key and secret.

Adding MapViewRepresentable to a View

The MapViewRepresentable is added to a SwiftUI ContentView. The MapModel is created as a @StateObject so it persists across view re-renders. Because the MapView lives on the model, SDK methods can be called directly from SwiftUI buttons and controls.

struct ContentView: View {
@StateObject private var model = MapModel()

var body: some View {
VStack {
MapViewRepresentable(model: model)
Button("Remove Labels") {
model.mapView.labels.removeAll()
}
}
}
}

Handling Click Events

SDK v6 uses an event-based system for handling user interactions. Subscribe to Events.click using mapView.on() to receive a ClickPayload whenever the user taps the map. The payload contains information about what was clicked, including labels, spaces, paths, floors, and the geographic coordinate.

The click event listener must be registered after the map is ready (inside the onMapReady callback), because the SDK only accepts event subscriptions once the map has loaded. The listener updates @Published properties on the MapModel, which drives a SwiftUI .alert in ContentView. Because the event callback may run off the main thread, wrap state updates in DispatchQueue.main.async:

// Inside MapViewRepresentable's onMapReady method:
mapView.on(Events.click) { [model] clickPayload in
guard let click = clickPayload else { return }
DispatchQueue.main.async {
model.alertTitle = click.floors?.first?.name ?? "Map Click"
var message = ""

if let labels = click.labels, !labels.isEmpty {
message.append("Label Clicked: \(labels.first?.text ?? "")\n")
}

if let spaces = click.spaces, !spaces.isEmpty {
message.append("Space Clicked: \(spaces.first?.name ?? "")\n")
}

if let paths = click.paths, !paths.isEmpty {
message.append("You clicked a path.\n")
}

message.append("Coordinate Clicked:\nLatitude: \(click.coordinate.latitude)\nLongitude: \(click.coordinate.longitude)")

model.alertMessage = message
model.showAlert = true
}
}

In ContentView, bind the alert to the model's published properties:

struct ContentView: View {
@StateObject private var model = MapModel()

var body: some View {
MapViewRepresentable(model: model)
.alert(model.alertTitle, isPresented: $model.showAlert) {
Button("Close", role: .cancel) {}
} message: {
Text(model.alertMessage)
}
}
}

Adding Interactive Labels

Labels can be added to spaces on the map using Labels.add(). Query for spaces using mapView.mapData.getByType(.space), then add a label to each named space. Setting interactive: true in AddLabelOptions allows labels to be included in click events.

mapView.mapData.getByType(.space) { (result: Result<[Space], Error>) in
if case .success(let spaces) = result {
spaces.forEach { space in
guard !space.name.isEmpty else { return }
mapView.labels.add(
target: space,
text: space.name,
options: AddLabelOptions(interactive: true)
)
}
}
}

Making Spaces Interactive

By default, spaces are not interactive. To enable click detection on spaces, update their state using MapView.updateState() with GeometryUpdateState(interactive: true):

mapView.mapData.getByType(.space) { (result: Result<[Space], Error>) in
if case .success(let spaces) = result {
spaces.forEach { space in
mapView.updateState(space: space, state: GeometryUpdateState(interactive: true))
}
}
}

Drawing Navigation Paths

Navigation paths can be drawn between locations using the Navigation class. First, get directions between two locations, then draw the path:

mapView.mapData.getByType(.enterpriseLocation) { (result: Result<[EnterpriseLocation], Error>) in
if case .success(let locations) = result {
let origin = locations.first(where: { $0.name == "Microsoft" })
let destination = locations.first(where: { $0.name == "Apple" })

if let origin = origin, let destination = destination {
mapView.mapData.getDirections(
from: .enterpriseLocation(origin),
to: .enterpriseLocation(destination)
) { dirResult in
if case .success(let directions?) = dirResult {
let pathOptions = AddPathOptions(interactive: true)
let navOptions = NavigationOptions(pathOptions: pathOptions)
mapView.navigation.draw(directions: directions, options: navOptions) { _ in }
}
}
}
}
}
tip

A complete example demonstrating SwiftUI can be found in the Mappedin iOS GitHub repo: SwiftUI With Mappedin Project implemented in the MapViewRepresentable.swift file.