Integrating MapsIndoors SDKs with Modern UI Frameworks

Introduction

As mobile app development evolves, declarative UI frameworks like SwiftUI for iOS and Jetpack Compose for Android have gained significant traction. These frameworks offer a more intuitive approach to building user interfaces with less code and greater flexibility. This guide explains how to integrate the MapsIndoors SDKs (which are built using traditional UIKit and Android Views) with these modern frameworks.

Understanding Modern UI Frameworks

The Declarative UI Paradigm

Modern UI frameworks like SwiftUI and Jetpack Compose use a declarative approach to building interfaces, which differs from the imperative approach used in traditional frameworks:

  • Traditional UI frameworks (UIKit, Android Views) require detailed, step-by-step instructions for creating and updating UI elements

  • Declarative frameworks allow developers to define what the UI should look like for a given state, and the framework handles the updates automatically

This approach simplifies development by reducing boilerplate code and making UI behavior more predictable.

Automatic Layout Adaptation

One significant advantage of SwiftUI and Jetpack Compose is their built-in layout engines that automatically adapt to different screen sizes and orientations. Rather than manually positioning elements for every possible configuration, these frameworks intelligently adjust layouts based on available space and device characteristics.

Integration Approach

The MapsIndoors SDKs can be seamlessly integrated with SwiftUI and Jetpack Compose through a simple wrapping process. This allows you to:

  1. Continue using the rich functionality of the MapsIndoors SDK

  2. Take advantage of the modern UI frameworks for the rest of your application

  3. Maintain a consistent development experience

Wrapping MapsIndoors Components

Since the core MapsIndoors SDK uses traditional UI components (UIKit for iOS and Android Views for Android), integration requires creating wrapper components that bridge these elements to the modern frameworks.

For SwiftUI

The Apple documentation explains how to wrap a UIKit view (which the MapsIndoors map view is for both Mapbox Maps and Google Maps) in their documentation, and they provide a tutorial with an example of how to use UIViewControllerRepresentable.

The approach is identical for using MapsIndoors with SwiftUI, where the Mapbox MapView or the Google Maps GMSMapView takes the place of the UIView that is wrapped. A simple example using Mapbox Maps to get you started is provided here.

class MapboxViewController: UIViewController {
    var solution: String!

    private var mapView: MapView!
    private var mapControl: MPMapControl?

    override public func viewDidLoad() {
        super.viewDidLoad()
        initializeMap()
        loadMapsIndoors()
    }

    private func initializeMap() {
        let cameraOptions = CameraOptions(
            center: CLLocationCoordinate2D(latitude: 40.75596, longitude: -73.97608),
            zoom: 23,
            bearing: 70,
            pitch: 50
        )
        let options = MapInitOptions(cameraOptions: cameraOptions)
        mapView = MapView(frame: view.bounds, mapInitOptions: options)
        self.view.addSubview(mapView)
    }

    private lazy var mapConfig = {
        let config = MPMapConfig(
            mapBoxView: mapView,
            accessToken: "<YOUR_MAPBOX_TOKEN>"
        )
        return config
    }()

    private func loadMapsIndoors() {
        guard let mapboxMap = mapView.mapboxMap else { return }
        mapboxMap.loadStyle(.standard) { _ in
            Task {
                do {
                    try await MPMapsIndoors.shared.load(apiKey: self.solution)
                    mapControl = MPMapsIndoors.createMapControl(mapConfig: mapConfig)
                    await self.goToInitialMapPosition()
                } catch {
                    print(error.localizedDescription)
                }
            }
        }
    }

    private func goToInitialMapPosition() async {
        let loc = await MPMapsIndoors.shared.locationsWith(query: nil, filter: nil).first!
        self.mapControl?.goTo(entity: loc, maxZoom: 21)
    }
}

struct MapsIndoorsNativeView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        let miView = MapboxViewController()
        miView.solution = solution.rawValue
        miView.delegate = context.coordinator
        return miView
    }

    func updateUIViewController(_: UIViewController, context _: Context) { }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

For Jetpack Compose

The Android documentation explains how to wrap a View (which the MapsIndoors MapControl uses for both Mapbox Maps and Google Maps) in a composition using the AndroidView composable.

The approach is identical for Mapbox Maps and Google Maps, using MapView with both. A simple example to get you started is provided here using Mapbox Maps:

class MainActivity : ComponentActivity(), OnViewReady {
   private var mapControl: MapControl? = null
   private var mapboxMap: MapboxMap? = null

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       enableEdgeToEdge()
       MapsIndoors.load(applicationContext, "YOUR_MAPSINDOORS_API_KEY") { e -> }
       setContent {
           ComposeExampleTheme {
               Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                   MapboxView(
                       mapInitOptions = MapInitOptions(baseContext),
                       modifier = Modifier.padding(innerPadding),
                       callback = this
                   )
               }
           }
       }
   }

   override fun onViewReady(mapView: MapView, mapboxMap: MapboxMap) {
       this.mapboxMap = mapboxMap
       val config = MPMapConfig.Builder(baseContext, mapboxMap, mapView, "YOUR_MAPBOX_API_KEY", true).build()
       MapControl.create(config) { mc, e ->
           mapControl = mc
           goToDefaultVenue()
       }
   }

   fun goToDefaultVenue() {
       mapControl?.goTo(MapsIndoors.getVenues()?.defaultVenue)
   }
}

@Composable
fun MapboxView(mapInitOptions: MapInitOptions, modifier: Modifier = Modifier, callback: OnViewReady? = null) {
   AndroidView(
       modifier = modifier,
       factory = { context ->
           MapView(context, mapInitOptions).also {
               callback?.onViewReady(mapView = it, mapboxMap = it.mapboxMap)
           }
       }
   )
}

interface OnViewReady {
   fun onViewReady(mapView: MapView, mapboxMap: MapboxMap)
}

Key Integration Considerations

When integrating MapsIndoors with these frameworks, keep in mind:

  • The core functionality of the MapsIndoors SDK remains unchanged

  • The wrapper simply handles the UI integration aspects

  • You can create custom wrappers tailored to your specific needs

  • The process is straightforward and requires minimal code

Benefits of Integration

Improved Developer Experience

By integrating MapsIndoors with SwiftUI or Jetpack Compose, you can:

  • Work with a more intuitive programming model

  • Reduce the amount of code needed for UI implementation

  • Benefit from live previews during development

  • More easily implement animations and transitions

Enhanced User Experience

The integration also improves the end-user experience by:

  • Ensuring UI consistency across your entire application

  • Enabling smoother integration with other modern UI components

  • Supporting more responsive layouts across device types

  • Facilitating more dynamic and interactive interfaces

Last updated

Was this helpful?