Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS/macOS observer hooks #3245

Merged
merged 11 commits into from
Feb 27, 2025
1 change: 1 addition & 0 deletions platform/darwin/bazel/files.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ MLN_DARWIN_OBJC_HEADERS = [
"src/MLNStyle.h",
"src/MLNStyleLayer.h",
"src/MLNStyleValue.h",
"src/MLNTileOperation.h",
"src/MLNTilePyramidOfflineRegion.h",
"src/MLNTileServerOptions.h",
"src/MLNTileSource.h",
Expand Down
13 changes: 13 additions & 0 deletions platform/darwin/src/MLNTileOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, MLNTileOperation) {
MLNTileOperationRequestedFromCache, ///< A read request from the cache
MLNTileOperationRequestedFromNetwork, ///< A read request from the online source
MLNTileOperationLoadFromNetwork, ///< Tile data from the network has been retrieved
MLNTileOperationLoadFromCache, ///< Tile data from the cache has been retrieved
MLNTileOperationStartParse, ///< Background processing of tile data has been initiated
MLNTileOperationEndParse, ///< Background processing of tile data has been completed
MLNTileOperationError, ///< An error occurred while loading the tile
MLNTileOperationCancelled, ///< Loading of a tile was cancelled
MLNTileOperationNullOp, ///< No operation has taken place
};
1 change: 1 addition & 0 deletions platform/ios/MapLibre.docc/MapLibre.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc

### Advanced

- <doc:ObserverExample>
- <doc:CustomStyleLayerExample>

### Other Articles
Expand Down
113 changes: 113 additions & 0 deletions platform/ios/MapLibre.docc/ObserverExample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Observe Low-Level Events

Learn about the ``MLNMapViewDelegate`` methods for observing map events.

> Warning: These methods are not thread-safe.

You can observe certain low-level events as they happen. Use these methods to collect metrics or investigate issues during map rendering. This feature is intended primarily for power users. We are always interested in improving observability, so if you have a special use case, feel free to [open an issue or pull request](https://github.com/maplibre/maplibre-native) to extend the types of observability methods.

## Shader Events

Observe shader compilation with ``MLNMapViewDelegate/mapView:shaderWillCompile:backend:defines:`` and ``MLNMapViewDelegate/mapView:shaderDidCompile:backend:defines:``.

<!-- include-example(ObserverExampleShaders) -->

```swift
func mapView(_: MLNMapView, shaderWillCompile id: Int, backend: Int, defines: String) {
print("A new shader is being compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
}

func mapView(_: MLNMapView, shaderDidCompile id: Int, backend: Int, defines: String) {
print("A shader has been compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
}
```

See also: ``MLNMapViewDelegate/mapView:shaderDidFailCompile:backend:defines:``.

## Glyph Loading

Observe glyph loading events with ``MLNMapViewDelegate/mapView:glyphsWillLoad:range:`` and ``MLNMapViewDelegate/mapView:glyphsDidLoad:range:``.

<!-- include-example(ObserverExampleGlyphs) -->

```swift
func mapView(_: MLNMapView, glyphsWillLoad fontStack: [String], range: NSRange) {
print("Glyphs are being requested for the font stack \(fontStack), ranging from \(range.location) to \(range.location + range.length)")
}

func mapView(_: MLNMapView, glyphsDidLoad fontStack: [String], range: NSRange) {
print("Glyphs have been loaded for the font stack \(fontStack), ranging from \(range.location) to \(range.location + range.length)")
}
```

See also: ``MLNMapViewDelegate/mapView:glyphsDidError:range:``.

## Tile Events

Monitor tile-related actions using the delegate method ``MLNMapViewDelegate/mapView:tileDidTriggerAction:x:y:z:wrap:overscaledZ:sourceID:`` with the ``MLNTileOperation`` type.

<!-- include-example(ObserverExampleTiles) -->

```swift
func mapView(_: MLNMapView, tileDidTriggerAction operation: MLNTileOperation,
x: Int,
y: Int,
z: Int,
wrap: Int,
overscaledZ: Int,
sourceID: String)
{
let tileStr = String(format: "(x: %ld, y: %ld, z: %ld, wrap: %ld, overscaledZ: %ld, sourceID: %@)",
x, y, z, wrap, overscaledZ, sourceID)

switch operation {
case MLNTileOperation.requestedFromCache:
print("Requesting tile \(tileStr) from cache")

case MLNTileOperation.requestedFromNetwork:
print("Requesting tile \(tileStr) from network")

case MLNTileOperation.loadFromCache:
print("Loading tile \(tileStr), requested from the cache")

case MLNTileOperation.loadFromNetwork:
print("Loading tile \(tileStr), requested from the network")

case MLNTileOperation.startParse:
print("Parsing tile \(tileStr)")

case MLNTileOperation.endParse:
print("Completed parsing tile \(tileStr)")

case MLNTileOperation.error:
print("An error occured during proccessing for tile \(tileStr)")

case MLNTileOperation.cancelled:
print("Pending work on tile \(tileStr)")

case MLNTileOperation.nullOp:
print("An unknown tile operation was emitted for tile \(tileStr)")

@unknown default:
assertionFailure()
}
}
```

## Sprite Loading

Observe sprite loading events with ``MLNMapViewDelegate/mapView:spriteWillLoad:url:`` and ``MLNMapViewDelegate/mapView:spriteDidLoad:url:``.

<!-- include-example(ObserverExampleSprites) -->

```swift
func mapView(_: MLNMapView, spriteWillLoad id: String, url: String) {
print("The sprite \(id) has been requested from \(url)")
}

func mapView(_: MLNMapView, spriteDidLoad id: String, url: String) {
print("The sprite \(id) has been loaded from \(url)")
}
```

See also: ``MLNMapViewDelegate/mapView:spriteDidError:url:``.
3 changes: 3 additions & 0 deletions platform/ios/app-swift/Sources/MapLibreNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ struct MapLibreNavigationView: View {
NavigationLink("AddMarkerExample") {
AddMarkerSymbolExampleUIViewControllerRepresentable()
}
NavigationLink("ObserverExample") {
ObserverExampleViewExampleUIViewControllerRepresentable()
}
Group {
NavigationLink("AnimatedLineExample") {
AnimatedLineExampleUIViewControllerRepresentable()
Expand Down
112 changes: 112 additions & 0 deletions platform/ios/app-swift/Sources/ObserverExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import MapLibre
import SwiftUI
import UIKit

class ObserverExampleView: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!

override func viewDidLoad() {
super.viewDidLoad()

mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

mapView.setCenter(
CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736),
zoomLevel: 11,
animated: false
)
view.addSubview(mapView)

mapView.delegate = self
}

// #-example-code(ObserverExampleShaders)
func mapView(_: MLNMapView, shaderWillCompile id: Int, backend: Int, defines: String) {
print("A new shader is being compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
}

func mapView(_: MLNMapView, shaderDidCompile id: Int, backend: Int, defines: String) {
print("A shader has been compiled - shaderID:\(id), backend type:\(backend), program configuration:\(defines)")
}

// #-end-example-code

// #-example-code(ObserverExampleGlyphs)
func mapView(_: MLNMapView, glyphsWillLoad fontStack: [String], range: NSRange) {
print("Glyphs are being requested for the font stack \(fontStack), ranging from \(range.location) to \(range.location + range.length)")
}

func mapView(_: MLNMapView, glyphsDidLoad fontStack: [String], range: NSRange) {
print("Glyphs have been loaded for the font stack \(fontStack), ranging from \(range.location) to \(range.location + range.length)")
}

// #-end-example-code

// #-example-code(ObserverExampleTiles)
func mapView(_: MLNMapView, tileDidTriggerAction operation: MLNTileOperation,
x: Int,
y: Int,
z: Int,
wrap: Int,
overscaledZ: Int,
sourceID: String)
{
let tileStr = String(format: "(x: %ld, y: %ld, z: %ld, wrap: %ld, overscaledZ: %ld, sourceID: %@)",
x, y, z, wrap, overscaledZ, sourceID)

switch operation {
case MLNTileOperation.requestedFromCache:
print("Requesting tile \(tileStr) from cache")

case MLNTileOperation.requestedFromNetwork:
print("Requesting tile \(tileStr) from network")

case MLNTileOperation.loadFromCache:
print("Loading tile \(tileStr), requested from the cache")

case MLNTileOperation.loadFromNetwork:
print("Loading tile \(tileStr), requested from the network")

case MLNTileOperation.startParse:
print("Parsing tile \(tileStr)")

case MLNTileOperation.endParse:
print("Completed parsing tile \(tileStr)")

case MLNTileOperation.error:
print("An error occured during proccessing for tile \(tileStr)")

case MLNTileOperation.cancelled:
print("Pending work on tile \(tileStr)")

case MLNTileOperation.nullOp:
print("An unknown tile operation was emitted for tile \(tileStr)")

@unknown default:
assertionFailure()
}
}

// #-end-example-code

// #-example-code(ObserverExampleSprites)
func mapView(_: MLNMapView, spriteWillLoad id: String, url: String) {
print("The sprite \(id) has been requested from \(url)")
}

func mapView(_: MLNMapView, spriteDidLoad id: String, url: String) {
print("The sprite \(id) has been loaded from \(url)")
}
// #-end-example-code
}

struct ObserverExampleViewExampleUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = ObserverExampleView

func makeUIViewController(context _: Context) -> ObserverExampleView {
ObserverExampleView()
}

func updateUIViewController(_: ObserverExampleView, context _: Context) {}
}
91 changes: 91 additions & 0 deletions platform/ios/app/MBXViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,97 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIVi

// MARK: - MLNMapViewDelegate

- (void)mapView:(MLNMapView *)mapView
shaderWillCompile:(NSInteger)id
backend:(NSInteger)backend
defines:(NSString *)defines
{
NSLog(@"A new shader is being compiled - shaderID:%ld, backend type:%ld, program configuration:%@", id, backend, defines);
}

- (void)mapView:(MLNMapView *)mapView
shaderDidCompile:(NSInteger)id
backend:(NSInteger)backend
defines:(NSString *)defines
{
NSLog(@"A shader has been compiled - shaderID:%ld, backend type:%ld, program configuration:%@", id, backend, defines);
}

- (void)mapView:(MLNMapView *)mapView
glyphsWillLoad:(NSArray<NSString *> *)fontStack
range:(NSRange)range
{
NSLog(@"Glyphs are being requested for the font stack %@, ranging from %ld to %ld", fontStack, range.location, range.location + range.length);
}

- (void)mapView:(MLNMapView *)mapView
glyphsDidLoad:(NSArray<NSString *> *)fontStack
range:(NSRange)range
{
NSLog(@"Glyphs have been loaded for the font stack %@, ranging from %ld to %ld", fontStack, range.location, range.location + range.length);
}

- (void)mapView:(MLNMapView *)mapView
tileDidTriggerAction:(MLNTileOperation)operation
x:(NSInteger)x
y:(NSInteger)y
z:(NSInteger)z
wrap:(NSInteger)wrap
overscaledZ:(NSInteger)overscaledZ
sourceID:(NSString *)sourceID
{
NSString* tileStr = [NSString stringWithFormat:@"(x: %ld, y: %ld, z: %ld, wrap: %ld, overscaledZ: %ld, sourceID: %@)",
x, y, z, wrap, overscaledZ, sourceID];

switch (operation) {
case MLNTileOperationRequestedFromCache:
NSLog(@"Requesting tile %@ from cache", tileStr);
break;

case MLNTileOperationRequestedFromNetwork:
NSLog(@"Requesting tile %@ from network", tileStr);
break;

case MLNTileOperationLoadFromCache:
NSLog(@"Loading tile %@, requested from the cache", tileStr);
break;

case MLNTileOperationLoadFromNetwork:
NSLog(@"Loading tile %@, requested from the network", tileStr);
break;

case MLNTileOperationStartParse:
NSLog(@"Parsing tile %@", tileStr);
break;

case MLNTileOperationEndParse:
NSLog(@"Completed parsing tile %@", tileStr);
break;

case MLNTileOperationError:
NSLog(@"An error occured during proccessing for tile %@", tileStr);
break;

case MLNTileOperationCancelled:
NSLog(@"Pending work on tile %@", tileStr);
break;

case MLNTileOperationNullOp:
NSLog(@"An unknown tile operation was emitted for tile %@", tileStr);
break;
}
}

- (void)mapView:(MLNMapView *)mapView spriteWillLoad:(NSString *)id url:(NSString *)url
{
NSLog(@"The sprite %@ has been requested from %@", id, url);
}

- (void)mapView:(MLNMapView *)mapView spriteDidLoad:(NSString *)id url:(NSString *)url
{
NSLog(@"The sprite %@ has been loaded from %@", id, url);
}

- (MLNAnnotationView *)mapView:(MLNMapView *)mapView viewForAnnotation:(id<MLNAnnotation>)annotation
{
if (annotation == mapView.userLocation)
Expand Down
1 change: 1 addition & 0 deletions platform/ios/sdk-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"MLNMapCamera.h": "platform/darwin/src/MLNMapCamera.h",
"MLNForegroundStyleLayer.h": "platform/darwin/src/MLNForegroundStyleLayer.h",
"MLNOfflineRegion.h": "platform/darwin/src/MLNOfflineRegion.h",
"MLNTileOperation.h": "platform/darwin/src/MLNTileOperation.h",
"MLNMapViewDelegate.h": "platform/ios/src/MLNMapViewDelegate.h",
"MLNDistanceFormatter.h": "platform/darwin/src/MLNDistanceFormatter.h",
"MLNCustomStyleLayer.h": "platform/darwin/src/MLNCustomStyleLayer.h",
Expand Down
15 changes: 15 additions & 0 deletions platform/ios/src/MLNMapView+Impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ class MLNMapViewImpl : public mbgl::MapObserver {
void onDidBecomeIdle() override;
void onStyleImageMissing(const std::string& imageIdentifier) override;
bool onCanRemoveUnusedStyleImage(const std::string& imageIdentifier) override;
void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override;
void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type,
const std::string&) override;
void onPostCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type,
const std::string&) override;
void onShaderCompileFailed(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type,
const std::string&) override;
void onGlyphsLoaded(const mbgl::FontStack&, const mbgl::GlyphRange&) override;
void onGlyphsError(const mbgl::FontStack&, const mbgl::GlyphRange&, std::exception_ptr) override;
void onGlyphsRequested(const mbgl::FontStack&, const mbgl::GlyphRange&) override;
void onTileAction(mbgl::TileOperation, const mbgl::OverscaledTileID&,
const std::string&) override;
void onSpriteLoaded(const std::optional<mbgl::style::Sprite>&) override;
void onSpriteError(const std::optional<mbgl::style::Sprite>&, std::exception_ptr) override;
void onSpriteRequested(const std::optional<mbgl::style::Sprite>&) override;

protected:
/// Cocoa map view that this adapter bridges to.
Expand Down
Loading
Loading