Mapbox iOS Integration Patterns
Official patterns for integrating Mapbox Maps SDK v11 on iOS with Swift, SwiftUI, and UIKit.
Use this skill when:
- Installing and configuring Mapbox Maps SDK for iOS
- Adding markers and annotations to maps
- Showing user location and tracking with camera
- Adding custom data (GeoJSON) to maps
- Working with map styles, camera, or user interaction
- Handling feature interactions and taps
Official Resources:
Installation & Setup
Requirements
- iOS 14+
- Xcode 15+
- Swift 5.9+
- Free Mapbox account
Step 1: Configure Access Token
Add your public token to Info.plist:
xml<key>MBXAccessToken</key> <string>pk.your_mapbox_token_here</string>
Get your token: Sign in at mapbox.com
Step 2: Add Swift Package Dependency
- File → Add Package Dependencies
- Enter URL:
https://github.com/mapbox/mapbox-maps-ios.git - Version: "Up to Next Major" from
11.0.0 - Verify four dependencies appear: MapboxCommon, MapboxCoreMaps, MapboxMaps, Turf
Alternative: CocoaPods or direct download (install guide)
Map Initialization
SwiftUI Pattern
Basic map:
swiftimport SwiftUI import MapboxMaps struct ContentView: View { @State private var viewport: Viewport = .camera( center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), zoom: 12 ) var body: some View { Map(viewport: $viewport) .mapStyle(.standard) } }
With ornaments:
swiftMap(viewport: $viewport) .mapStyle(.standard) .ornamentOptions(OrnamentOptions( scaleBar: .init(visibility: .visible), compass: .init(visibility: .adaptive), logo: .init(position: .bottomLeading) ))
UIKit Pattern
swiftimport UIKit import MapboxMaps class MapViewController: UIViewController { private var mapView: MapView! override func viewDidLoad() { super.viewDidLoad() let options = MapInitOptions( cameraOptions: CameraOptions( center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), zoom: 12 ) ) mapView = MapView(frame: view.bounds, mapInitOptions: options) mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(mapView) mapView.mapboxMap.loadStyle(.standard) } }
Add Markers
The SDK offers three ways to place a point on the map. Pick the simplest one that fits.
Agent note: A SwiftUI Mapbox sketch is incomplete without at least one annotation (Marker, PointAnnotation, or MapViewAnnotation). Do not ship a bare Map { } with no pin.
Which API should I use?
| API | Use it when | Platforms | Notes |
|---|---|---|---|
Marker (Markers API) | You need a default pin and don't have a custom image asset | SwiftUI only | No image assets required. Experimental SPI — needs @_spi(Experimental) import MapboxMaps. Best < 100 markers. |
PointAnnotation | You have a custom image and want layer-level placement | SwiftUI + UIKit | Backed by a symbol layer, so it scales well to hundreds of markers. Accepts any UIImage that UIKit can render. |
View annotations (ViewAnnotation / MapViewAnnotation) | You want to render a full native view (card, badge, animated content) anchored to a coordinate | SwiftUI + UIKit | SwiftUI uses MapViewAnnotation; UIKit uses mapView.viewAnnotations with a ViewAnnotation. Each annotation is a real view — costs more than PointAnnotation at scale. |
For hundreds or thousands of features, use a style layer (SymbolLayer on a GeoJSONSource) instead of annotations.
Markers API (recommended for simple cases, SwiftUI)
swiftimport SwiftUI @_spi(Experimental) import MapboxMaps struct ContentView: View { var body: some View { Map { Marker(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)) .color(.red) .text("San Francisco") } } }
Multiple markers from a collection:
swiftMap { ForEvery(locations, id: \.id) { location in Marker(coordinate: location.coordinate) .color(.red) .text(location.name) } }
Scaling note.
MarkerandPointAnnotationeach create their own view or symbol entry per pin — fine up to about 100 markers. For larger datasets (hundreds or thousands of features — common with open-ended GeoJSON feeds), load the data into aGeoJSONSourceand render it with aSymbolLayerinstead. That scales to thousands of features and enables clustering.
PointAnnotation (custom image)
SwiftUI:
swiftMap(viewport: $viewport) { PointAnnotation(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)) .image(.init(image: UIImage(named: "marker")!, name: "marker")) }
UIKit:
swift// Create annotation manager (once, reuse for updates) var pointAnnotationManager = mapView.annotations.makePointAnnotationManager() // Create marker var annotation = PointAnnotation(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)) annotation.image = .init(image: UIImage(named: "marker")!, name: "marker") annotation.iconAnchor = .bottom // Add to map pointAnnotationManager.annotations = [annotation]
Multiple markers:
swiftlet annotations = locations.map { coordinate in var annotation = PointAnnotation(coordinate: coordinate) annotation.image = .init(image: UIImage(named: "marker")!, name: "marker") return annotation } pointAnnotationManager.annotations = annotations
Show User Location
Step 1: Add location permission to Info.plist:
xml<key>NSLocationWhenInUseUsageDescription</key> <string>Show your location on the map</string>
Step 2: Request permissions and show location:
swiftimport CoreLocation // Request permissions let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // Show user location puck mapView.location.options.puckType = .puck2D() mapView.location.options.puckBearingEnabled = true
Performance Best Practices
Reuse Annotation Managers
swift// ❌ Don't create new managers repeatedly func updateMarkers() { let manager = mapView.annotations.makePointAnnotationManager() manager.annotations = markers } // ✅ Create once, reuse let pointAnnotationManager: PointAnnotationManager init() { pointAnnotationManager = mapView.annotations.makePointAnnotationManager() } func updateMarkers() { pointAnnotationManager.annotations = markers }
Batch Annotation Updates
swift// ✅ Update all at once pointAnnotationManager.annotations = newAnnotations // ❌ Don't update one by one for annotation in newAnnotations { pointAnnotationManager.annotations.append(annotation) }
Memory Management
swift// Use weak self in closures mapView.gestures.onMapTap.observe { [weak self] context in self?.handleTap(context.coordinate) }.store(in: &cancelables) // Clean up on deinit deinit { cancelables.forEach { $0.cancel() } }
Use Standard Style
swift// ✅ Standard style is optimized and recommended .mapStyle(.standard) // Use other styles only when needed for specific use cases .mapStyle(.standardSatellite) // Satellite imagery
Troubleshooting
Map Not Displaying
Check:
- ✅
MBXAccessTokenin Info.plist - ✅ Token is valid (test at mapbox.com)
- ✅ MapboxMaps framework imported
- ✅ MapView added to view hierarchy
- ✅ Correct frame/constraints set
Style Not Loading
swiftmapView.mapboxMap.onStyleLoaded.observe { [weak self] _ in print("Style loaded successfully") // Add layers and sources here }.store(in: &cancelables)
Performance Issues
- Use
.standardstyle (recommended and optimized) - Limit visible annotations to viewport
- Reuse annotation managers
- Avoid frequent style reloads
- Batch annotation updates
Reference Files
Load these references when the task requires deeper patterns:
references/annotations.md— Circle, Polyline, Polygon Annotationsreferences/location-tracking.md— Camera Follow User + Get Current Locationreferences/custom-data.md— GeoJSON: Lines, Polygons, Points, Update/Removereferences/camera-styles.md— Camera Control + Map Stylesreferences/interactions.md— Featureset Interactions, Custom Layer Taps, Long Press, Gestures

