Skip to content

Commit 544197d

Browse files
evil159github-actions[bot]
authored andcommitted
Expose MapboxMap.onStyleAttributionsChanged event (#10729)
GitOrigin-RevId: 5b1b53b0e04fa6e34f141bf29210e16692f6c98c
1 parent 79aefea commit 544197d

File tree

7 files changed

+80
-52
lines changed

7 files changed

+80
-52
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Mapbox welcomes participation and contributions from everyone.
55
## main
66

77
* Fix incorrect positioning of map ornaments when multiple ornaments are placed in the same corner.
8+
* Expose `MapboxMap.onStyleAttributionsChanged`, use this event to observe when attributions have been changed due to style change, source metadata change, or if sources were removed or added.
9+
810
## 11.20.0-rc.1 - 03 March, 2026
911

1012
## 11.19.0 - 24 February, 2026

Sources/Examples/All Examples/Lab/MapEventsExample.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class MapEventsExample: UIViewController, ExampleProtocol {
4747
logEvent(map.onStyleImageRemoveUnused)
4848
// onResourceRequest produces too much logs for demonstration, uncomment it if needed.
4949
// logEvent(mapView.mapboxMap.onResourceRequest)
50+
logEvent(map.onStyleAttributionsChanged)
5051

5152
map.onCameraChanged.observe { [weak self] event in
5253
self?.cameraLabel.attributedText = .formatted(cameraSate: event.cameraState)
@@ -230,6 +231,11 @@ extension ResourceRequest: LogableEvent {
230231
var info: String { "ti: \(timeInterval), source: \(source), url: \(request.url)" }
231232
}
232233

234+
extension StyleAttributionsChanged: LogableEvent {
235+
var name: String { "StyleAttributionsChanged" }
236+
var info: String { "ts: \(timestamp), attributions: \(attributions)" }
237+
}
238+
233239
extension Optional where Wrapped: CustomStringConvertible {
234240
var log: String {
235241
switch self {

Sources/MapboxMaps/Foundation/MapboxMap.swift

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ protocol MapboxMapProtocol: AnyObject {
6262
var onRenderFrameStarted: Signal<RenderFrameStarted> { get }
6363
var onRenderFrameFinished: Signal<RenderFrameFinished> { get }
6464
var onResourceRequest: Signal<ResourceRequest> { get }
65+
var onStyleAttributionsChanged: Signal<StyleAttributionsChanged> { get }
6566

6667
func dispatch(event: CorePlatformEventInfo)
6768
@discardableResult func addInteraction(_ interaction: some Interaction) -> Cancelable
@@ -1435,82 +1436,89 @@ extension MapboxMap {
14351436
/// The style has been fully loaded, and the map has rendered all visible tiles.
14361437
public var onMapLoaded: Signal<MapLoaded> { events.signal(for: \.onMapLoaded) }
14371438

1438-
/// An error that has occurred while loading the Map. The `type` property defines what resource could
1439-
/// not be loaded and the `message` property will contain a descriptive error message.
1440-
/// In case of `source` or `tile` loading errors, `sourceID` or `tileID` will contain the identifier of the source failing.
1439+
/// An error that has occurred while loading the Map. The `type` property defines what resource could
1440+
/// not be loaded and the `message` property will contain a descriptive error message.
1441+
/// In case of `source` or `tile` loading errors, `sourceID` or `tileID` will contain the identifier of the source failing.
14411442
public var onMapLoadingError: Signal<MapLoadingError> { events.signal(for: \.onMapLoadingError) }
14421443

1443-
/// The requested style has been fully loaded, including the style, specified sprite and sources' metadata.
1444-
///
1445-
/// The style specified sprite would be marked as loaded even with sprite loading error (an error will be emitted via ``MapboxMap/onMapLoadingError``).
1446-
/// Sprite loading error is not fatal and we don't want it to block the map rendering, thus this event will still be emitted if style and sources are fully loaded.
1444+
/// The requested style has been fully loaded, including the style, specified sprite and sources' metadata.
1445+
///
1446+
/// The style specified sprite would be marked as loaded even with sprite loading error (an error will be emitted via ``MapboxMap/onMapLoadingError``).
1447+
/// Sprite loading error is not fatal and we don't want it to block the map rendering, thus this event will still be emitted if style and sources are fully loaded.
14471448
public var onStyleLoaded: Signal<StyleLoaded> { events.signal(for: \.onStyleLoaded) }
14481449

1449-
/// The requested style data has been loaded. The `type` property defines what kind of style data has been loaded.
1450-
/// Event may be emitted synchronously, for example, when ``MapboxMap/loadStyle(_:transition:completion:)-1ilz1`` is used to load style.
1451-
///
1452-
/// Based on an event data `type` property value, following use-cases may be implemented:
1453-
/// - `style`: Style is parsed, style layer properties could be read and modified, style layers and sources could be
1454-
/// added or removed before rendering is started.
1455-
/// - `sprite`: Style's sprite sheet is parsed and it is possible to add or update images.
1456-
/// - `sources`: All sources defined by the style are loaded and their properties could be read and updated if needed.
1450+
/// The requested style data has been loaded. The `type` property defines what kind of style data has been loaded.
1451+
/// Event may be emitted synchronously, for example, when ``MapboxMap/loadStyle(_:transition:reloadPolicy:completion:)`` is used to load style.
1452+
///
1453+
/// Based on an event data `type` property value, following use-cases may be implemented:
1454+
/// - `style`: Style is parsed, style layer properties could be read and modified, style layers and sources could be
1455+
/// added or removed before rendering is started.
1456+
/// - `sprite`: Style's sprite sheet is parsed and it is possible to add or update images.
1457+
/// - `sources`: All sources defined by the style are loaded and their properties could be read and updated if needed.
14571458
public var onStyleDataLoaded: Signal<StyleDataLoaded> { events.signal(for: \.onStyleDataLoaded) }
14581459

1459-
/// The camera has changed. This event is emitted whenever the visible viewport
1460-
/// changes due to the MapView's size changing or when the camera
1461-
/// is modified by calling camera methods. The event is emitted synchronously,
1462-
/// so that an updated camera state can be fetched immediately.
1460+
/// The camera has changed. This event is emitted whenever the visible viewport
1461+
/// changes due to the MapView's size changing or when the camera
1462+
/// is modified by calling camera methods. The event is emitted synchronously,
1463+
/// so that an updated camera state can be fetched immediately.
14631464
public var onCameraChanged: Signal<CameraChanged> { events.signal(for: \.onCameraChanged) }
14641465

1465-
/// The map has entered the idle state. The map is in the idle state when there are no ongoing transitions
1466-
/// and the map has rendered all requested non-volatile tiles. The event will not be emitted if animation is in progress (see ``MapboxMap/beginAnimation()``, ``MapboxMap/endAnimation()``)
1467-
/// and / or gesture is in progress (see ``MapboxMap/beginGesture()``, ``MapboxMap/endGesture()``).
1466+
/// The map has entered the idle state. The map is in the idle state when there are no ongoing transitions
1467+
/// and the map has rendered all requested non-volatile tiles. The event will not be emitted if animation is in progress (see ``MapboxMap/beginAnimation()``, ``MapboxMap/endAnimation()``)
1468+
/// and / or gesture is in progress (see ``MapboxMap/beginGesture()``, ``MapboxMap/endGesture()``).
14681469
public var onMapIdle: Signal<MapIdle> { events.signal(for: \.onMapIdle) }
14691470

1470-
/// The source has been added with ``StyleManager/addSource(_:dataId:)`` or ``StyleManager/addSource(withId:properties:)``.
1471-
/// The event is emitted synchronously, therefore, it is possible to immediately
1472-
/// read added source's properties.
1471+
/// The source has been added with ``StyleManager/addSource(_:dataId:)`` or ``StyleManager/addSource(withId:properties:)``.
1472+
/// The event is emitted synchronously, therefore, it is possible to immediately
1473+
/// read added source's properties.
14731474
public var onSourceAdded: Signal<SourceAdded> { events.signal(for: \.onSourceAdded) }
14741475

1475-
/// The source has been removed with ``StyleManager/removeSource(withId:)``.
1476-
/// The event is emitted synchronously, thus, ``StyleManager/allSourceIdentifiers`` will be
1477-
/// in sync when the observer receives the notification.
1476+
/// The source has been removed with ``StyleManager/removeSource(withId:)``.
1477+
/// The event is emitted synchronously, thus, ``StyleManager/allSourceIdentifiers`` will be
1478+
/// in sync when the observer receives the notification.
14781479
public var onSourceRemoved: Signal<SourceRemoved> { events.signal(for: \.onSourceRemoved) }
14791480

1480-
/// A source data has been loaded.
1481-
/// Event may be emitted synchronously in cases when source's metadata is available when source is added to the style.
1482-
///
1483-
/// The `dataID` property defines the source id.
1484-
///
1485-
/// The `type` property defines if source's metadata (e.g., TileJSON) or tile has been loaded. The property of `metadata`
1486-
/// value might be useful to identify when particular source's metadata is loaded, thus all source's properties are
1487-
/// readable and can be updated before map will start requesting data to be rendered.
1488-
///
1489-
/// The `loaded` property will be set to `true` if all source's data required for visible viewport of the map, are loaded.
1490-
/// The `tileID` property defines the tile id if the `type` field equals `tile`.
1491-
/// The `dataID` property will be returned if it has been set for this source.
1481+
/// A source data has been loaded.
1482+
/// Event may be emitted synchronously in cases when source's metadata is available when source is added to the style.
1483+
///
1484+
/// The `dataID` property defines the source id.
1485+
///
1486+
/// The `type` property defines if source's metadata (e.g., TileJSON) or tile has been loaded. The property of `metadata`
1487+
/// value might be useful to identify when particular source's metadata is loaded, thus all source's properties are
1488+
/// readable and can be updated before map will start requesting data to be rendered.
1489+
///
1490+
/// The `loaded` property will be set to `true` if all source's data required for visible viewport of the map, are loaded.
1491+
/// The `tileID` property defines the tile id if the `type` field equals `tile`.
1492+
/// The `dataID` property will be returned if it has been set for this source.
14921493
public var onSourceDataLoaded: Signal<SourceDataLoaded> { events.signal(for: \.onSourceDataLoaded) }
14931494

1494-
/// A style has a missing image. This event is emitted when the map renders visible tiles and
1495-
/// one of the required images is missing in the sprite sheet. Subscriber has to provide the missing image
1496-
/// by calling ``StyleManager/addImage(_:id:sdf:contentInsets:)``.
1495+
/// A style has a missing image. This event is emitted when the map renders visible tiles and
1496+
/// one of the required images is missing in the sprite sheet. Subscriber has to provide the missing image
1497+
/// by calling ``StyleManager/addImage(_:id:sdf:contentInsets:)``.
14971498
public var onStyleImageMissing: Signal<StyleImageMissing> { events.signal(for: \.onStyleImageMissing) }
14981499

1499-
/// An image added to the style is no longer needed and can be removed using ``StyleManager/removeImage(withId:)``.
1500+
/// An image added to the style is no longer needed and can be removed using ``StyleManager/removeImage(withId:)``.
15001501
public var onStyleImageRemoveUnused: Signal<StyleImageRemoveUnused> { events.signal(for: \.onStyleImageRemoveUnused) }
15011502

1502-
/// The map started rendering a frame.
1503+
/// The map started rendering a frame.
15031504
public var onRenderFrameStarted: Signal<RenderFrameStarted> { events.signal(for: \.onRenderFrameStarted) }
15041505

1505-
/// The map finished rendering a frame.
1506-
/// The `renderMode` property tells whether the map has all data (`full`) required to render the visible viewport.
1507-
/// The `needsRepaint` property provides information about ongoing transitions that trigger map repaint.
1508-
/// The `placementChanged` property tells if the symbol placement has been changed in the visible viewport.
1506+
/// The map finished rendering a frame.
1507+
/// The `renderMode` property tells whether the map has all data (`full`) required to render the visible viewport.
1508+
/// The `needsRepaint` property provides information about ongoing transitions that trigger map repaint.
1509+
/// The `placementChanged` property tells if the symbol placement has been changed in the visible viewport.
15091510
public var onRenderFrameFinished: Signal<RenderFrameFinished> { events.signal(for: \.onRenderFrameFinished) }
15101511

1511-
/// The `ResourceRequest` event allows client to observe resource requests made by a
1512-
/// map or snapshotter.
1513-
public var onResourceRequest: Signal<ResourceRequest> { events.signal(for: \.onResourceRequest) }
1512+
/// The `ResourceRequest` event allows client to observe resource requests made by a
1513+
/// map or snapshotter.
1514+
public var onResourceRequest: Signal<ResourceRequest> { events.signal(for: \.onResourceRequest) }
1515+
1516+
/// The style attributions has been changed. The event will be emitted every time attributions have been changed
1517+
/// due to style change, source metadata change, or if sources were removed or added.
1518+
///
1519+
/// Note: The event may be emitted synchronously, for example, when `removeSource` was called
1520+
/// or if newly added source doesn't require to load metadata from network.
1521+
public var onStyleAttributionsChanged: Signal<StyleAttributionsChanged> { events.signal(for: \.onStyleAttributionsChanged) }
15141522

15151523
/// Returns a ``Signal`` that allows to subscribe to the event with specified string name.
15161524
/// This method is reserved for the future use.

Sources/MapboxMaps/MapEvents/MapEvents.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal final class MapEvents {
2020
let onRenderFrameStarted: SignalSubject<RenderFrameStarted>
2121
let onRenderFrameFinished: SignalSubject<RenderFrameFinished>
2222
let onResourceRequest: SignalSubject<ResourceRequest>
23+
let onStyleAttributionsChanged: SignalSubject<StyleAttributionsChanged>
2324
let makeGenericSubject: (String) -> SignalSubject<GenericEvent>
2425

2526
private var genericSubjects = [String: SignalSubject<GenericEvent>]()
@@ -40,6 +41,7 @@ internal final class MapEvents {
4041
onRenderFrameStarted = .from(method: observable.subscribe(forRenderFrameStarted:))
4142
onRenderFrameFinished = .from(method: observable.subscribe(forRenderFrameFinished:))
4243
onResourceRequest = .from(method: observable.subscribe(forResourceRequest:))
44+
onStyleAttributionsChanged = .from(method: observable.subscribe(forStyleAttributionsChanged:))
4345
makeGenericSubject = { eventName in
4446
.from(parameter: eventName, method: observable.subscribe(forEventName:callback:))
4547
}
@@ -61,6 +63,7 @@ internal final class MapEvents {
6163
onRenderFrameStarted = SignalSubject()
6264
onRenderFrameFinished = SignalSubject()
6365
onResourceRequest = SignalSubject()
66+
onStyleAttributionsChanged = SignalSubject()
6467
self.makeGenericSubject = makeGenericSubject
6568
}
6669

Sources/MapboxMaps/SwiftUI/Map+Events.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,9 @@ public extension Map {
120120
func onResourceRequest(action: @escaping (ResourceRequest) -> Void) -> Self {
121121
copyAppended(self, \.mapDependencies.eventsSubscriptions, AnyEventSubscription(keyPath: \.onResourceRequest, perform: action))
122122
}
123+
124+
/// Adds an action to perform when style attributions are changed.
125+
func onStyleAttributionsChanged(action: @escaping (StyleAttributionsChanged) -> Void) -> Self {
126+
copyAppended(self, \.mapDependencies.eventsSubscriptions, AnyEventSubscription(keyPath: \.onStyleAttributionsChanged, perform: action))
127+
}
123128
}

Tests/MapboxMapsTests/Foundation/MapboxMapTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ final class MapboxMapTests: XCTestCase {
442442
loadingMethod: [NSNumber(value: RequestLoadingMethodType.network.rawValue)]),
443443
response: nil, cancelled: false, timeInterval: timeInterval)
444444
checkEvent(\.onResourceRequest, \.onResourceRequest, value: resourceRequest)
445+
446+
let styleAttributionsChanged = StyleAttributionsChanged(attributions: ["foo", "bar"], timestamp: Date())
447+
checkEvent(\.onStyleAttributionsChanged, \.onStyleAttributionsChanged, value: styleAttributionsChanged)
445448
}
446449

447450
func testGenericEvents() {

Tests/MapboxMapsTests/Foundation/Mocks/MockMapboxMap.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ final class MockMapboxMap: MapboxMapProtocol {
2424
var onRenderFrameStarted: Signal<RenderFrameStarted> { events.signal(for: \.onRenderFrameStarted) }
2525
var onRenderFrameFinished: Signal<RenderFrameFinished> { events.signal(for: \.onRenderFrameFinished) }
2626
var onResourceRequest: Signal<ResourceRequest> { events.signal(for: \.onResourceRequest) }
27+
var onStyleAttributionsChanged: Signal<StyleAttributionsChanged> { events.signal(for: \.onStyleAttributionsChanged)}
2728

2829
@TestPublished var size: CGSize = .zero
2930
var sizeSignal: Signal<CGSize> { $size }

0 commit comments

Comments
 (0)