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

MapViewPort implementation to listen to raw values of 'camera' #33

Merged
merged 2 commits into from
Apr 16, 2024
Merged
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
4 changes: 3 additions & 1 deletion Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct MapView: UIViewRepresentable {

var gestures = [MapGesture]()
var onStyleLoaded: ((MLNStyle) -> Void)?
var onViewPortChanged: ((MapViewPort) -> Void)?

public var mapViewContentInset: UIEdgeInsets = .zero
public var isLogoViewHidden = false
Expand Down Expand Up @@ -42,7 +43,8 @@ public struct MapView: UIViewRepresentable {
public func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(
parent: self,
onGesture: { processGesture($0, $1) }
onGesture: { processGesture($0, $1) },
onViewPortChanged: { onViewPortChanged?($0) }
)
}

Expand Down
26 changes: 25 additions & 1 deletion Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ public class MapViewCoordinator: NSObject {

var onStyleLoaded: ((MLNStyle) -> Void)?
var onGesture: (MLNMapView, UIGestureRecognizer) -> Void
var onViewPortChanged: (MapViewPort) -> Void

init(parent: MapView,
onGesture: @escaping (MLNMapView, UIGestureRecognizer) -> Void)
onGesture: @escaping (MLNMapView, UIGestureRecognizer) -> Void,
onViewPortChanged: @escaping (MapViewPort) -> Void)
{
self.parent = parent
self.onGesture = onGesture
self.onViewPortChanged = onViewPortChanged
}

// MARK: Core UIView Functionality
Expand Down Expand Up @@ -205,6 +208,8 @@ extension MapViewCoordinator: MLNMapViewDelegate {
onStyleLoaded?(mglStyle)
}

// MARK: MapViewCamera

@MainActor private func updateParentCamera(mapView: MLNMapView, reason: MLNCameraChangeReason) {
// If any of these are a mismatch, we know the camera is no longer following a desired method, so we should
// detach and revert to a .centered camera. If any one of these is true, the desired camera state still
Expand Down Expand Up @@ -247,6 +252,12 @@ extension MapViewCoordinator: MLNMapViewDelegate {

/// The MapView's region has changed with a specific reason.
public func mapView(_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool) {
// FIXME: CI complains about MainActor.assumeIsolated being unavailable before iOS 17, despite building on iOS 17.2... This is an epic hack to fix it for now. I can only assume this is an issue with Xcode pre-15.3
// TODO: We could put this in regionIsChangingWith if we calculate significant change/debounce.
Task { @MainActor in
updateViewPort(mapView: mapView)
}

guard !suppressCameraUpdatePropagation else {
return
}
Expand All @@ -256,4 +267,17 @@ extension MapViewCoordinator: MLNMapViewDelegate {
updateParentCamera(mapView: mapView, reason: reason)
}
}

// MARK: MapViewPort

@MainActor private func updateViewPort(mapView: MLNMapView) {
// Calculate the Raw "ViewPort"
let calculatedViewPort = MapViewPort(
center: mapView.centerCoordinate,
zoom: mapView.zoomLevel,
direction: mapView.direction
)

onViewPortChanged(calculatedViewPort)
}
}
6 changes: 6 additions & 0 deletions Sources/MapLibreSwiftUI/MapViewModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,10 @@ public extension MapView {

return result
}

func onMapViewPortUpdate(_ onViewPortChanged: @escaping (MapViewPort) -> Void) -> Self {
var result = self
result.onViewPortChanged = onViewPortChanged
return result
}
}
41 changes: 41 additions & 0 deletions Sources/MapLibreSwiftUI/Models/MapViewPort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import CoreLocation
import Foundation

/// A representation of the MapView's current ViewPort.
///
/// This includes similar data to the MapViewCamera, but represents the raw
/// values associated with the MapView. This information could used to prepare
/// a new MapViewCamera on a scene, to cache the camera state, etc.
public struct MapViewPort: Hashable, Equatable {
/// The current center coordinate of the MapView
public let center: CLLocationCoordinate2D

/// The current zoom value of the MapView
public let zoom: Double

/// The current compass direction of the MapView
public let direction: CLLocationDirection

public init(center: CLLocationCoordinate2D, zoom: Double, direction: CLLocationDirection) {
self.center = center
self.zoom = zoom
self.direction = direction
}

public static func zero(zoom: Double = 10) -> MapViewPort {
MapViewPort(center: CLLocationCoordinate2D(latitude: 0, longitude: 0),
zoom: zoom,
direction: 0)
}
}

public extension MapViewPort {
/// Generate a basic MapViewCamera that represents the MapViewPort
///
/// - Returns: The calculated MapViewCamera
func asMapViewCamera() -> MapViewCamera {
.center(center,
zoom: zoom,
direction: direction)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
mapView = MapView(styleURL: URL(string: "https://maplibre.org")!)
coordinator = MapView.Coordinator(parent: mapView) { _, _ in
// No action
} onViewPortChanged: { _ in
// No action
}
}

Expand Down
Loading