View annotations
Add view annotations on top of the map view using the Mapbox Maps SDK's View Annotations API. View annotations are UIKit views that the SDK draws on top of a Mapbox MapView
and can be bound to a Geometry
or to a feature rendered on a Layer
. This can be helpful for achieving common patterns used in maps in mobile applications such as showing an information window when tapping a point of interest (POI), or providing estimated time of arrival (ETA) for navigation purposes along a route.
Currently the following Layer types are supported:-
LineLayer
-
FillLayer
-
FillExtrusionLayer
-
CircleLayer
-
SymbolLayer
Benefits:
- Straight-forward API that allows adding an iOS
UIView
as a view annotation, making it possible to add clickable buttons or any other UI elements. - High rendering performance when using a reasonable number of views (generally below 100, but may vary based on the view's content and the user's device model).
- Wide number of options including allowing overlap between view annotations, selected state, connecting to a map feature, and more.
Limitations:
- Inefficient and poor performance when adding many features (> 250) to the map if allow overlap is turned on.
Create a view annotation
Create a UIView
Start by creating a new UIView
or subclass of UIView
that contains the contents of the view annotation. This can include anything from text to images to interactive elements and more.
For example, to create a minimal UIView
containing text:
private func createSampleView(withText text: String) -> UILabel {
let label = UILabel()
label.text = text
label.font = .systemFont(ofSize: 14)
label.numberOfLines = 0
label.textColor = .black
label.backgroundColor = .white
label.textAlignment = .center
return label
}
Add a view annotation to the MapView
To add the view annotation on top of the MapView
, start by:
- Creating and configuring a
ViewAnnotation
with the sample view from above. - Passing the sample view and options to the map view.
Add the view annotation on view load:
override func viewDidLoad() {
super.viewDidLoad()
// Define a coordinate
let centerCoordinate = CLLocationCoordinate2D(latitude: 39.7128, longitude: -75.0060)
// Add a map view
let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 7))
mapView = MapView(frame: view.bounds, mapInitOptions: options)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
// Add the view annotation
let viewAnnotation = ViewAnnotation(coordinate: centerCoordinate, view: createSampleView(withText: "Hello world!"))
mapView.viewAnnotations.add(viewAnnotation)
}
In SwiftUI you may achieve the similar behavior using MapViewAnnotation
and SwiftUI.Text
instead of creating UILabel
let centerCoordinate = CLLocationCoordinate2D(latitude: 39.7128, longitude: -75.0060)
Map(initialViewport: .camera(center: centerCoordinate, zoom: 7)) {
Map(initialViewport: .camera(center: .apple, zoom: 10)) {
MapViewAnnotation(coordinate: .apple) {
Text("Hello world!")
.font(.system(size: 14))
.lineLimit(0)
.foregroundColor(.black)
.multilineTextAlignment(.center)
.background(Color.white)
}
.selected(true) // To bring a view annotation on top of others regardless of the order in which it was added
}
}
There are many more options available when adding or updating the view
annotation. See the complete list of options in
theViewAnnotation
documentation.
Customize the appearance
Specify order
The z-index of view annotations is based on the order in which they are added. This allows you to control which view is visible when multiple views intersect on the screen.
To bring a view annotation on top of others regardless of the order in which it was added, use the selected
property:
viewAnnotation.selected = true
Any number of view annotations could be in selected state. The z-index among selected view annotations also respects the order in which they are added.
Control behavior related to camera
View annotations do not rotate, pitch, or scale with the map, but some properties (including width, height, and additional offset in pixels) can be controlled by the user using ViewAnnotationAnchorConfig
.
You can provide multiple anchor configurations for an annotation, map renderer will automatically pick the first suitable anchor position depending on position relative to other view annotations or its annotated feature on the map, and notifies the change of anchor using ViewAnnotation/onAnchorChanged
.
@State selectedAnchor: ViewAnnotationAnchorConfig?
Map() {
MapViewAnnotation(coordinate: .apple) { EmptyView() }
.variableAnchors([
ViewAnnotationAnchorConfig(anchor: .top, offsetX: 10, offsetY: 10),
ViewAnnotationAnchorConfig(anchor: .bottom)
])
.onAnchorChanged { config in
selectedAnchor = config
}
}
// Allow top and bottom anchor directions.
viewAnnotation.variableAnchors = [
ViewAnnotationAnchorConfig(anchor: .top, offsetX: 10, offsetY: 10),
ViewAnnotationAnchorConfig(anchor: .bottom)
]
viewAnnotation.onAnchorChanged = { config in
// Update the view's anchor to the newly picked one.
view.anchor = config.anchor
}
Handle visibility
When a view annotation is added or updated, visibility can be specified explicitly. An annotation becomes hidden when it goes out of map view’s bounds or visible property is changed. You can listen to the event when the annotation's visibility has changed using ViewAnnotation/onVisibilityChanged
.
viewAnnotation.visible = false
viewAnnotation.onVisibilityChanged = { isVisible in
// Handle visibility changes here
}
Do not change the UIView
's isHidden
property directly. When setting
visible
to false, it will set the view to hidden and also remove it from the
layout calculation.
Handle content size updates
You do not need to explicitly set size for a view annotation, however, when you do some operation that results in content size changed, you should call ViewAnnotation/setNeedsUpdateSize()
to update the annotation size.
updateViewSize(width: 100, height: 100)
viewAnnotation.setNeedsUpdateSize() // Updates the annotation size.
Common uses
The flexibility of view annotations means that there are countless ways to use this feature, but there are a few common uses outlined below to help you get started.
Display on map click
You can add view annotations when a user interacts with the map.
When the user clicks on the map, you can get information from the map camera, position (like in the example linked below), or data in the map to determine the contents of the view annotation using one of MapboxMap
's map feature query methods.
Add view annotation on a map click.
Attach to a geometry
You can tie a view annotation to a geometry, for example, a line string, this allows the annotation to stay visible on screen along with the visible part of the line string.
Use Dynamic View Annotations, Style, and Viewport API to create navigation experience
Attach to an annotated feature
You can tie a view annotation's visibility to an annotated feature's visibility using AnnotatedFeature
. This allows implementing marker pop-ups with a minimal amount of code and makes the view annotation disappear automatically if the point annotation image is not visible due to overlap or collision.
Map() {
PointAnnotation(id: "some-id", coordinate: coordinate)
.iconImage("marker-image-name")
.iconAnchor(.bottom)
MapViewAnnotation(layerId: "some-id") { Text("Annotation view") }
.variableAnchors([
ViewAnnotationAnchorConfig(anchor: .bottom, offsetY: markerImageHeight)
])
}
// prepare point annotation
var pointAnnotation = PointAnnotation(coordinate: coordinate)
pointAnnotation.iconImage = "marker-image-name"
pointAnnotation.iconAnchor = .bottom
let pointAnnotationManager = mapView.annotations.makePointAnnotationManager()
pointAnnotationManager.annotations = [pointAnnotation]
// prepare view annotation and connect it by feature id
let annotationView = AnnotationView(frame: CGRect(x: 0, y: 0, width: 128, height: 64))
let viewAnnotation = ViewAnnotation(
annotatedFeature: .layerFeature(layerId: pointAnnotationManager.layerId, featureId: pointAnnotation.id),
view: annotationView)
viewAnnotation.variableAnchors = [ViewAnnotationAnchorConfig(anchor: .bottom, offsetY: markerImageHeight)]
mapView.viewAnnotations.add(annotation)
Add view annotation anchored to a point annotation.
While the View Annotation API does not come with built-in logic for handling opening and closing a callout attached to a symbol layer feature when the feature is click, you can see one possible way to implement that functionality in the Add view annotations connected to features.
Add view annotation anchored to a symbol layer feature.