Skip to content

Add logging to MapView delegate callbacks and camera updates. #83

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

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Sources/InternalUtils/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os

public extension Logger {
/// For more information see:
/// - https://developer.apple.com/documentation/os/viewing-log-messages
/// For even more details see:
/// - https://developer.apple.com/forums/thread/705868
init(category: String) {
self.init(subsystem: "MapLibre-SwiftUI", category: category)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import Mockable

// NOTE: We should eventually mark the entire protocol @MainActor, but Mockable generates some unsafe code at the moment
@Mockable

Check warning on line 7 in Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift

View workflow job for this annotation

GitHub Actions / platform=iOS Simulator,name=iPhone 16,OS=18.1

Your SwiftSyntax version is pinned to 510.x.x by some of your dependencies. Using a lower SwiftSyntax version than your Swift version may lead to issues when using Mockable. (from macro 'Mockable')

Check warning on line 7 in Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift

View workflow job for this annotation

GitHub Actions / platform=iOS Simulator,name=iPhone 16,OS=18.1

Your SwiftSyntax version is pinned to 510.x.x by some of your dependencies. Using a lower SwiftSyntax version than your Swift version may lead to issues when using Mockable. (from macro 'Mockable')

Check warning on line 7 in Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift

View workflow job for this annotation

GitHub Actions / platform=iOS Simulator,name=iPhone 16,OS=18.1

Your SwiftSyntax version is pinned to 510.x.x by some of your dependencies. Using a lower SwiftSyntax version than your Swift version may lead to issues when using Mockable. (from macro 'Mockable')
public protocol MLNMapViewCameraUpdating: AnyObject {
@MainActor var userTrackingMode: MLNUserTrackingMode { get set }
@MainActor func setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?)
Expand All @@ -28,8 +28,12 @@
animated: Bool,
completionHandler: (() -> Void)?
)

@MainActor var activityIdentifier: String { get }
}

extension MLNMapView: MLNMapViewCameraUpdating {
// No definition
public var activityIdentifier: String {
MapActivity.loggingValue(tag)
}
}
29 changes: 27 additions & 2 deletions Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import InternalUtils
import MapLibre
import MapLibreSwiftDSL
import os
import SwiftUI

private extension Logger {
static let uiViewControllerRepresentable = Logger(category: "UIViewControllerRepresentable")
}

/// Identifies the activity this ``MapView`` is being used for. Useful for debugging purposes.
public enum MapActivity: Int {
public enum MapActivity: Int, CustomStringConvertible {
/// Navigation in a standard window. Default.
case standard = 0
/// Navigation in a CarPlay template.
case carplay = 2025

public var description: String {
switch self {
case .standard:
"standard"
case .carplay:
"carplay"
}
}

static func loggingValue(_ rawValue: Self.RawValue) -> String {
guard let activity = MapActivity(rawValue: rawValue) else { return String(rawValue) }
return activity.description
}
}

public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentable {
Expand Down Expand Up @@ -60,7 +79,9 @@ public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentab
}

public func makeCoordinator() -> MapViewCoordinator<T> {
MapViewCoordinator<T>(
Logger.uiViewControllerRepresentable
.debug("\(#function, privacy: .public), activity: \(activity, privacy: .public)")
return MapViewCoordinator<T>(
parent: self,
onGesture: { processGesture($0, $1) },
onViewProxyChanged: { onViewProxyChanged?($0) },
Expand All @@ -69,6 +90,8 @@ public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentab
}

public func makeUIViewController(context: Context) -> T {
Logger.uiViewControllerRepresentable
.debug("\(#function, privacy: .public), activity: \(activity, privacy: .public)")
// Create the map view
let controller = makeViewController()
controller.mapView.delegate = context.coordinator
Expand Down Expand Up @@ -105,6 +128,8 @@ public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentab
}

public func updateUIViewController(_ uiViewController: T, context: Context) {
Logger.uiViewControllerRepresentable
.debug("\(#function, privacy: .public), activity: \(activity, privacy: .public)")
context.coordinator.parent = self

applyModifiers(uiViewController, runUnsafe: true)
Expand Down
50 changes: 48 additions & 2 deletions Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import Foundation
import MapLibre
import MapLibreSwiftDSL
import os

private extension Logger {
static let mlnMapViewDelegate = Logger(category: "MLNMapViewDelegate")
static let cameraUpdate = Logger(category: "cameraUpdate")
}

extension MLNCameraChangeReason: @retroactive CustomStringConvertible {
private static let descriptions: [(Self, String)] = [
(.programmatic, "programmatic"),
(.resetNorth, "resetNorth"),
(.gesturePan, "gesturePan"),
(.gesturePinch, "gesturePinch"),
(.gestureRotate, "gestureRotate"),
(.gestureZoomIn, "gestureZoomIn"),
(.gestureZoomOut, "gestureZoomOut"),
(.gestureOneFingerZoom, "gestureOneFingerZoom"),
(.gestureTilt, "gestureTilt"),
(.transitionCancelled, "transitionCancelled"),
]

public var description: String {
var names = Self.descriptions.filter { contains($0.0) }.map(\.1)
if names.isEmpty { names = ["none"] }
return names.joined(separator: ",")
}
}

public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, @preconcurrency
MLNMapViewDelegate {
Expand Down Expand Up @@ -116,7 +143,7 @@
layer is MLNSymbolStyleLayer
}

for layerSpec in parent.userLayers {

Check warning on line 146 in Sources/MapLibreSwiftUI/MapViewCoordinator.swift

View workflow job for this annotation

GitHub Actions / platform=iOS Simulator,name=iPhone 16,OS=18.1

main actor-isolated property 'userLayers' can not be referenced from a nonisolated context; this is an error in the Swift 6 language mode
// DISCUSS: What preventions should we try to put in place against the user accidentally adding the same layer twice?
let newLayer = layerSpec.makeStyleLayer(style: mglStyle).makeMLNStyleLayer()

Expand Down Expand Up @@ -178,6 +205,11 @@
return
}

Logger.cameraUpdate
.debug(
"camera: \(camera, privacy: .public) frame: \(NSCoder.string(for: mapView.frame), privacy: .public) animated: \(animated, privacy: .public) activity: \(mapView.activityIdentifier, privacy: .public)"
)

snapshotCamera = camera

// Cancel any existing camera update completion task.
Expand Down Expand Up @@ -373,15 +405,25 @@

// MARK: - MLNMapViewDelegate

public func mapView(_: MLNMapView, didFinishLoading mglStyle: MLNStyle) {
@MainActor
public func mapView(_ mapView: MLNMapView, didFinishLoading mglStyle: MLNStyle) {
Logger.mlnMapViewDelegate
.debug(
"\(#function, privacy: .public) style: \(mglStyle, privacy: .public) activity: \(mapView.activityIdentifier, privacy: .public)"
)
addLayers(to: mglStyle)
onStyleLoaded?(mglStyle)
}

/// The MapView's region has changed with a specific reason.
@MainActor
public func mapView(
_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool
_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated: Bool
) {
Logger.mlnMapViewDelegate
.debug(
"\(#function, privacy: .public) reason: \(reason, privacy: .public) animated: \(animated, privacy: .public) activity: \(mapView.activityIdentifier, privacy: .public)"
)
// TODO: We could put this in regionIsChangingWith if we calculate significant change/debounce.
MainActor.assumeIsolated {
// regionIsChangingWith is not called for the final update, so we need to call updateViewProxy
Expand Down Expand Up @@ -412,6 +454,10 @@

@MainActor
public func mapView(_ mapView: MLNMapView, regionIsChangingWith reason: MLNCameraChangeReason) {
Logger.mlnMapViewDelegate
.debug(
"\(#function, privacy: .public) reason: \(reason, privacy: .public) activity: \(mapView.activityIdentifier, privacy: .public)"
)
if proxyUpdateMode == .realtime {
updateViewProxy(mapView: mapView, reason: reason)
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import MapLibre
/// The SwiftUI MapViewCamera.
///
/// This manages the camera state within the MapView.
public struct MapViewCamera: Hashable, Equatable, Sendable {
public struct MapViewCamera: Hashable, Equatable, Sendable, CustomStringConvertible {
public enum Defaults {
public static let coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
public static let zoom: Double = 10
Expand Down Expand Up @@ -139,4 +139,8 @@ public struct MapViewCamera: Hashable, Equatable, Sendable {
MapViewCamera(state: .rect(boundingBox: box, edgePadding: edgePadding),
lastReasonForChange: .programmatic)
}

public var description: String {
"State: \(state) last: \((lastReasonForChange != nil) ? "\(lastReasonForChange!)" : "nil")"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
)
.willReturn()

given(maplibreMapView)
.activityIdentifier
.willReturn("standard")

try await simulateCameraUpdateAndWait {
self.coordinator.applyCameraChangeFromStateUpdate(
self.maplibreMapView, camera: camera, animated: false
Expand Down Expand Up @@ -95,6 +99,10 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
)
.willReturn()

given(maplibreMapView)
.activityIdentifier
.willReturn("standard")

try await simulateCameraUpdateAndWait {
self.coordinator.applyCameraChangeFromStateUpdate(
self.maplibreMapView, camera: newCamera, animated: false
Expand Down Expand Up @@ -140,6 +148,10 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
.setZoomLevel(.any, animated: .any)
.willReturn()

given(maplibreMapView)
.activityIdentifier
.willReturn("standard")

try await simulateCameraUpdateAndWait {
self.coordinator.applyCameraChangeFromStateUpdate(
self.maplibreMapView, camera: newCamera, animated: false
Expand Down Expand Up @@ -185,6 +197,10 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
.setZoomLevel(.any, animated: .any)
.willReturn()

given(maplibreMapView)
.activityIdentifier
.willReturn("standard")

try await simulateCameraUpdateAndWait {
self.coordinator.applyCameraChangeFromStateUpdate(
self.maplibreMapView, camera: newCamera, animated: false
Expand Down Expand Up @@ -230,6 +246,10 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
.setZoomLevel(.any, animated: .any)
.willReturn()

given(maplibreMapView)
.activityIdentifier
.willReturn("standard")

try await simulateCameraUpdateAndWait {
self.coordinator.applyCameraChangeFromStateUpdate(
self.maplibreMapView, camera: newCamera, animated: false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
▿ MapViewCamera
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ State: CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 24.6993808, longitude: 46.7709285), southwest: CLLocationCoordinate2D(latitude: 24.6056011, longitude: 46.67369842529297), edgePadding: UIEdgeInsets(top: 20.0, left: 20.0, bottom: 20.0, right: 20.0)) last: programmatic
▿ state: CameraState
▿ rect: (2 elements)
▿ boundingBox: MLNCoordinateBounds
Expand All @@ -15,3 +13,5 @@
- left: 20.0
- right: 20.0
- top: 20.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
▿ MapViewCamera
- lastReasonForChange: Optional<CameraChangeReason>.none
▿ State: CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 5.0, pitch: 12.0, pitchRange: free, direction: 23.0) last: nil
▿ state: CameraState
▿ centered: (5 elements)
▿ onCoordinate: CLLocationCoordinate2D
Expand All @@ -9,3 +8,4 @@
- pitch: 12.0
- pitchRange: CameraPitchRange.free
- direction: 23.0
- lastReasonForChange: Optional<CameraChangeReason>.none
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
▿ MapViewCamera
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ State: CameraState.trackingUserLocationWithCourse(zoom: (18.0, 0.0, MapLibreSwiftUI.CameraPitchRange.freeWithinRange(minimum: 12.0, maximum: 34.0))) last: programmatic
▿ state: CameraState
▿ trackingUserLocationWithCourse: (3 elements)
- zoom: 18.0
Expand All @@ -9,3 +7,5 @@
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
▿ MapViewCamera
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ State: CameraState.trackingUserLocationWithHeading(zoom: (10.0, 0.0, MapLibreSwiftUI.CameraPitchRange.free)) last: programmatic
▿ state: CameraState
▿ trackingUserLocationWithHeading: (3 elements)
- zoom: 10.0
- pitch: 0.0
- pitchRange: CameraPitchRange.free
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
▿ MapViewCamera
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ State: CameraState.trackingUserLocation(zoom: (10.0, 0.0, MapLibreSwiftUI.CameraPitchRange.freeWithinRange(minimum: 12.0, maximum: 34.0), 0.0)) last: programmatic
▿ state: CameraState
▿ trackingUserLocation: (4 elements)
- zoom: 10.0
Expand All @@ -10,3 +8,5 @@
- minimum: 12.0
- maximum: 34.0
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
Loading