Skip to content

Commit 1edeb3f

Browse files
pjleonard37github-actions[bot]
authored andcommitted
[MAPSSDK-851] Add Animation support to Marker on Android and iOS (#7661)
This PR adds pre-defined animations to Markers for both iOS and Android, enabling developers to easily animate markers when they appear (marker is added to the map) or disappear (removed from map). There are three pre-defined animations, which can be combined GitOrigin-RevId: 37a14f5b0ffa8c7adc9d2b0421b0114507a5f17b
1 parent a31050a commit 1edeb3f

11 files changed

Lines changed: 818 additions & 135 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ Mapbox welcomes participation and contributions from everyone.
44

55
## main
66

7+
### Features ✨ and improvements 🏁
8+
* Add animation to experimental `Marker` with two animation triggers: `appear` and `disappear`. Each trigger accepts `MarkerAnimationEffect` including `wiggle` (pendulum rotation), `scale`, `fadeIn`, and `fadeOut`. Effects can be customized with parameters (e.g., `scale(from: 0.5, to: 1.5)`, `fade(from: 0.5, to: 1.0)`) and combined for rich animations. See `MarkersExample` for usage.
9+
710
## 11.19.0-rc.1 - 12 February, 2026
811

912
### Features ✨ and improvements 🏁
1013
* Introduce new `LineLayer.lineElevationGroundScale` property to scale elevated lines with terrain exaggeration.
1114
* Promote elevated lines properties to stable: `LineLayer.lineZOffset` and `LineLayer.lineElevationReference`.
1215
* Introduce experimental `SymbolScaleBehavior` API to automatically scale map symbols (icons and text) based on system accessibility text size settings. Set `MapboxMap.symbolScaleBehavior` property to configure: `.system` (automatic scaling), `.system(mapping:)` (custom mapping function), or `.fixed(scaleFactor:)` (fixed scale, default is 1.0). Valid scale factor range is [0.8, 2.0]. Automatic scaling is opt-in; symbols default to fixed 1.0x scale.
13-
14-
### Features ✨ and improvements 🏁
1516
* Add `ModelSource` support with `Model`, `ModelMaterialOverride`, and `ModelNodeOverride` to enable interactive 3D models. Material overrides allow customization of color, emissive strength, opacity, and color mix intensity. Node overrides enable control of model part transformations such as rotating doors, landing gear, or propellers. Models can be updated via source-driven approach (modifying `ModelSource.models` directly) or feature-state driven approach (using expressions with feature state for dynamic control). For implementation examples, see `Interactive3DModelFeatureStateExample` (SwiftUI), `Interactive3DModelSourceExample` (UIKit), and `Animated3DModelSourceExample` (SwiftUI).
1617

1718
## 11.19.0-beta.1 - 28 January, 2026

Sources/Examples/SwiftUI Examples/MarkersExample.swift

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,138 @@ struct MarkersExample: View {
88
@State private var showStroke: Bool = true
99
@State private var showText: Bool = true
1010
@State private var overlayHeight: CGFloat = 0
11-
@State private var markerText: String = "Central Helsinki"
12-
@State private var tappedPoints = [CLLocationCoordinate2D]()
11+
@State private var showPresetMarkers: Bool = true
12+
13+
// Helsinki coordinates for the map center
14+
private let centerCoord = CLLocationCoordinate2D(latitude: 60.1699, longitude: 24.9384)
15+
16+
// User-added markers
17+
@State private var userMarkers: [MarkerData] = []
1318

1419
var body: some View {
15-
Map(initialViewport: .camera(center: .helsinki, zoom: 15, pitch: 60)) {
16-
Marker(coordinate: .helsinki)
17-
.color(markerColor)
18-
.stroke(showStroke ? strokeColor : nil)
19-
.text(showText ? markerText : nil)
20-
.onTapGesture {
21-
markerColor = .random
22-
}
20+
Map(initialViewport: .camera(center: centerCoord, zoom: 13, pitch: 45)) {
21+
// Three preset markers demonstrating different animation types
22+
if showPresetMarkers {
23+
let coord1 = CLLocationCoordinate2D(latitude: 60.1699, longitude: 24.9384 - 0.01)
24+
Marker(coordinate: coord1)
25+
.color(markerColor)
26+
.stroke(showStroke ? strokeColor : nil)
27+
.text(showText ? "Wiggle+Scale" : nil)
28+
.animation(.wiggle, .scale, when: .appear)
29+
.animation(.wiggle, .scale(from: 1, to: 0), when: .disappear)
30+
.onTapGesture {
31+
print("Marker tapped at: \(coord1.latitude), \(coord1.longitude)")
32+
}
2333

24-
ForEvery(tappedPoints, id: \.latitude) { coord in
25-
Marker(coordinate: coord)
34+
let coord2 = CLLocationCoordinate2D(latitude: 60.1699, longitude: 24.9384)
35+
Marker(coordinate: coord2)
2636
.color(markerColor)
2737
.stroke(showStroke ? strokeColor : nil)
28-
.text(showText ? String(format: "%.3f, %.3f", coord.latitude, coord.longitude) : nil)
38+
.text(showText ? "Scale" : nil)
39+
.animation(.scale, when: .appear)
40+
.animation(.scale(from: 1, to: 0), when: .disappear)
2941
.onTapGesture {
30-
markerColor = .random
42+
print("Marker tapped at: \(coord2.latitude), \(coord2.longitude)")
43+
}
44+
45+
let coord3 = CLLocationCoordinate2D(latitude: 60.1699, longitude: 24.9384 + 0.01)
46+
Marker(coordinate: coord3)
47+
.color(markerColor)
48+
.stroke(showStroke ? strokeColor : nil)
49+
.text(showText ? "Fade" : nil)
50+
.animation(.fadeIn, when: .appear)
51+
.animation(.fadeOut, when: .disappear)
52+
.onTapGesture {
53+
print("Marker tapped at: \(coord3.latitude), \(coord3.longitude)")
3154
}
3255
}
3356

34-
TapInteraction { tapContext in
35-
tappedPoints.append(tapContext.coordinate)
36-
return true
57+
// User-added markers
58+
ForEvery(userMarkers, id: \.id) { markerData in
59+
Marker(coordinate: markerData.coordinate)
60+
.color(markerColor)
61+
.stroke(showStroke ? strokeColor : nil)
62+
.text(showText ? String(format: "%.2f, %.2f", markerData.coordinate.latitude, markerData.coordinate.longitude) : nil)
63+
.animation(.wiggle, .scale, when: .appear)
64+
.animation(.wiggle, .scale(from: 1, to: 0), when: .disappear)
65+
.onTapGesture {
66+
print("Marker tapped at: \(markerData.coordinate.latitude), \(markerData.coordinate.longitude)")
67+
}
68+
}
69+
70+
TapInteraction { context in
71+
addMarker(at: context.coordinate)
72+
return false
3773
}
3874

3975
LongPressInteraction { _ in
40-
tappedPoints.removeAll()
41-
return true
76+
userMarkers.removeAll()
77+
showPresetMarkers = false
78+
return false
4279
}
4380
}
44-
.additionalSafeAreaInsets(.bottom, overlayHeight)
45-
.ignoresSafeArea(edges: [.all] )
81+
.additionalSafeAreaInsets(Edge.Set.bottom, overlayHeight)
82+
.ignoresSafeArea(edges: [.all])
4683
.overlay(alignment: .bottom) {
47-
VStack(alignment: .leading) {
48-
Text("Tap to add a Marker")
49-
ColorPicker("Marker Color", selection: $markerColor)
50-
ColorPicker("Stroke Color", selection: $strokeColor)
51-
Toggle("Show stroke", isOn: $showStroke)
52-
Toggle("Show Marker text", isOn: $showText)
53-
}
54-
.padding(.horizontal, 10)
55-
.padding(.vertical, 6)
56-
.floating(RoundedRectangle(cornerRadius: 10))
57-
.limitPaneWidth()
58-
.onChangeOfSize { size in
59-
overlayHeight = size.height
84+
HStack {
85+
Spacer()
86+
VStack(alignment: .leading, spacing: 8) {
87+
Text("Marker Animations")
88+
.font(.headline)
89+
90+
VStack(alignment: .leading, spacing: 2) {
91+
Text("• Three preset markers show different animation types")
92+
.font(.caption2)
93+
Text("• Tap the map to add new markers with animation")
94+
.font(.caption2)
95+
Text("• Long press the map to remove all markers")
96+
.font(.caption2)
97+
}
98+
.foregroundColor(.secondary)
99+
100+
Divider()
101+
102+
// Styling Controls
103+
Text("Styling")
104+
.font(.subheadline)
105+
.fontWeight(.semibold)
106+
107+
ColorPicker("Marker Color", selection: $markerColor)
108+
ColorPicker("Stroke Color", selection: $strokeColor)
109+
110+
Toggle("Show stroke", isOn: $showStroke)
111+
Toggle("Show text", isOn: $showText)
112+
}
113+
.padding(.horizontal, 10)
114+
.padding(.vertical, 6)
115+
.floating(RoundedRectangle(cornerRadius: 10))
116+
.limitPaneWidth()
117+
.onChangeOfSize { size in
118+
overlayHeight = size.height
119+
}
120+
Spacer()
60121
}
61122
}
62123
}
124+
125+
private func addMarker(at coordinate: CLLocationCoordinate2D) {
126+
let newMarker = MarkerData(
127+
id: UUID().uuidString,
128+
coordinate: coordinate
129+
)
130+
userMarkers.append(newMarker)
131+
}
63132
}
64133

134+
// MARK: - MarkerData
135+
136+
struct MarkerData: Identifiable {
137+
let id: String
138+
let coordinate: CLLocationCoordinate2D
139+
}
140+
141+
// MARK: - Previews
142+
65143
struct MarkersExample_Previews: PreviewProvider {
66144
static var previews: some View {
67145
MarkersExample()

Sources/Examples/SwiftUI Examples/Testing Examples/SwiftUIRoot.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct SwiftUIExamples {
2121
Example("Animated 3D airplane model", note: "Animate a 3D airplane model along a flight path with animated propellers, landing gear, and lights using feature state.", destination: Animated3DModelSourceExample())
2222
},
2323
Examples.Category("Annotations") {
24-
Example("Add Map Markers", note: "Add/remove Markers to your map.", destination: MarkersExample())
24+
Example("Add Map Markers", note: "Add Markers to your map.", destination: MarkersExample())
2525
Example("View Annotations", note: "Add/remove view annotation on tap.", destination: ViewAnnotationsExample())
2626
Example("Weather annotations", note: "Show view annotations with contents changed on selection.", destination: WeatherAnnotationExample())
2727
Example("Layer Annotations", note: "Add/remove layer annotation on tap.", destination: AnnotationsExample())

0 commit comments

Comments
 (0)