Mapbox SDK Integration into a Mobile Application
Mapbox is the choice when you need full control over the visual style of your map: custom colors, fonts, layer hiding, and adding your own data sources on top of the base map. Mapbox GL / Mapbox Maps SDK v11 work with vector tiles and render the map natively through Metal (iOS) or OpenGL ES / Vulkan (Android) — hence the smooth rotation and zoom without pixelization.
Versioning: v10 vs v11
Mapbox Maps SDK for Android v11 (2023+) and iOS v11 are essentially a new SDK with a different API compared to v9/v10. Key changes: MapboxMap instead of MapboxMapOptions, ViewAnnotation instead of custom MarkerView, PointAnnotationManager instead of SymbolManager. If you find examples with addMarker() — that's v9, not applicable.
Integration
Android:
// settings.gradle
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
authentication { create<BasicAuthentication>("basic") }
credentials {
username = "mapbox"
password = providers.gradleProperty("MAPBOX_DOWNLOADS_TOKEN").get()
}
}
}
}
// build.gradle (app)
implementation("com.mapbox.maps:android:11.7.0")
The download token (MAPBOX_DOWNLOADS_TOKEN) is separate from the public access token. Both are required.
iOS (SPM):
// In Xcode: File → Add Package Dependencies
// URL: https://github.com/mapbox/mapbox-maps-ios
// Version: 11.x
Access token is set in Info.plist with the key MBXAccessToken or programmatically:
MapboxOptions.accessToken = "pk.eyJ1IjoiLi4uIn0..."
Basic Map with Custom Style
import MapboxMaps
class MapViewController: UIViewController {
var mapView: MapView!
override func viewDidLoad() {
super.viewDidLoad()
let cameraOptions = CameraOptions(
center: CLLocationCoordinate2D(latitude: 55.7558, longitude: 37.6173),
zoom: 12
)
let initOptions = MapInitOptions(
cameraOptions: cameraOptions,
styleURI: StyleURI(rawValue: "mapbox://styles/your-username/your-style-id")
// or StyleURI.streets / .dark / .satellite
)
mapView = MapView(frame: view.bounds, mapInitOptions: initOptions)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
}
}
Styles are created in Mapbox Studio — a web editor with full control over each layer. Changes in Studio are published instantly without updating the application.
PointAnnotationManager: Markers in v11
mapView.mapboxMap.onMapLoaded.observeNext { [weak self] _ in
guard let self else { return }
var annotationManager = self.mapView.annotations.makePointAnnotationManager()
var annotations = [PointAnnotation]()
let points: [(Double, Double, String)] = [
(55.7558, 37.6173, "Center"),
(55.7518, 37.6105, "Arbat")
]
for (lat, lng, title) in points {
var annotation = PointAnnotation(
coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lng)
)
annotation.image = .init(image: UIImage(named: "custom-pin")!, name: "custom-pin")
annotation.iconSize = 1.0
annotation.textField = title
annotation.textOffset = [0, 2]
annotations.append(annotation)
}
annotationManager.annotations = annotations
annotationManager.annotationInteractionDelegate = self
}
Android: Adding a GeoJSON Layer
Mapbox allows you to add your own GeoJSON as a data source and render it via a style — this is more powerful than markers for a large number of points:
mapView.mapboxMap.loadStyle(Style.MAPBOX_STREETS) { style ->
// Add source
style.addSource(
GeoJsonSource.Builder("locations-source")
.data("""
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": { "type": "Point", "coordinates": [37.6173, 55.7558] },
"properties": { "name": "Point A" }
}
]
}
""".trimIndent())
.build()
)
// Add symbol layer
style.addLayer(
SymbolLayer("locations-layer", "locations-source").apply {
iconImage("custom-icon")
iconSize(1.0)
textField(get("name"))
textOffset(listOf(0.0, 1.5))
}
)
}
For 10,000+ points, this is the only performant approach — PointAnnotationManager with thousands of objects begins to degrade FPS.
Offline Maps
val offlineManager = OfflineManager()
val tileStore = TileStore.create()
val tilesetDescriptor = offlineManager.createTilesetDescriptor(
TilesetDescriptorOptions.Builder()
.styleURI(Style.MAPBOX_STREETS)
.minZoom(10)
.maxZoom(16)
.build()
)
val tileRegionLoadOptions = TileRegionLoadOptions.Builder()
.geometry(Point.fromLngLat(37.6173, 55.7558)) // or Polygon for area
.descriptors(listOf(tilesetDescriptor))
.build()
tileStore.loadTileRegion("moscow-center", tileRegionLoadOptions, { progress ->
Log.d("Mapbox", "Downloaded: ${progress.completedResourceCount}/${progress.requiredResourceCount}")
}, { result ->
result.fold({ region -> Log.d("Mapbox", "Done: ${region.id}") }, { error -> })
})
Timeline
1–3 days. Basic map with custom style — 1 day. GeoJSON layers, offline, clustering — 2–3 days. Cost is calculated individually.







