Skip to content

Commit c7fc6a0

Browse files
authored
Merge pull request #33 from Rallista/feat/map-view-port
MapViewPort implementation to listen to raw values of 'camera'
2 parents 6e0cf32 + 42e6907 commit c7fc6a0

File tree

5 files changed

+77
-2
lines changed

5 files changed

+77
-2
lines changed

Sources/MapLibreSwiftUI/MapView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public struct MapView: UIViewRepresentable {
1212
var gestures = [MapGesture]()
1313

1414
var onStyleLoaded: ((MLNStyle) -> Void)?
15+
var onViewPortChanged: ((MapViewPort) -> Void)?
1516

1617
public var mapViewContentInset: UIEdgeInsets = .zero
1718

@@ -42,7 +43,8 @@ public struct MapView: UIViewRepresentable {
4243
public func makeCoordinator() -> MapViewCoordinator {
4344
MapViewCoordinator(
4445
parent: self,
45-
onGesture: { processGesture($0, $1) }
46+
onGesture: { processGesture($0, $1) },
47+
onViewPortChanged: { onViewPortChanged?($0) }
4648
)
4749
}
4850

Sources/MapLibreSwiftUI/MapViewCoordinator.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ public class MapViewCoordinator: NSObject {
1919

2020
var onStyleLoaded: ((MLNStyle) -> Void)?
2121
var onGesture: (MLNMapView, UIGestureRecognizer) -> Void
22+
var onViewPortChanged: (MapViewPort) -> Void
2223

2324
init(parent: MapView,
24-
onGesture: @escaping (MLNMapView, UIGestureRecognizer) -> Void)
25+
onGesture: @escaping (MLNMapView, UIGestureRecognizer) -> Void,
26+
onViewPortChanged: @escaping (MapViewPort) -> Void)
2527
{
2628
self.parent = parent
2729
self.onGesture = onGesture
30+
self.onViewPortChanged = onViewPortChanged
2831
}
2932

3033
// MARK: Core UIView Functionality
@@ -205,6 +208,8 @@ extension MapViewCoordinator: MLNMapViewDelegate {
205208
onStyleLoaded?(mglStyle)
206209
}
207210

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

248253
/// The MapView's region has changed with a specific reason.
249254
public func mapView(_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool) {
255+
// 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
256+
// TODO: We could put this in regionIsChangingWith if we calculate significant change/debounce.
257+
Task { @MainActor in
258+
updateViewPort(mapView: mapView)
259+
}
260+
250261
guard !suppressCameraUpdatePropagation else {
251262
return
252263
}
@@ -256,4 +267,17 @@ extension MapViewCoordinator: MLNMapViewDelegate {
256267
updateParentCamera(mapView: mapView, reason: reason)
257268
}
258269
}
270+
271+
// MARK: MapViewPort
272+
273+
@MainActor private func updateViewPort(mapView: MLNMapView) {
274+
// Calculate the Raw "ViewPort"
275+
let calculatedViewPort = MapViewPort(
276+
center: mapView.centerCoordinate,
277+
zoom: mapView.zoomLevel,
278+
direction: mapView.direction
279+
)
280+
281+
onViewPortChanged(calculatedViewPort)
282+
}
259283
}

Sources/MapLibreSwiftUI/MapViewModifiers.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,10 @@ public extension MapView {
123123

124124
return result
125125
}
126+
127+
func onMapViewPortUpdate(_ onViewPortChanged: @escaping (MapViewPort) -> Void) -> Self {
128+
var result = self
129+
result.onViewPortChanged = onViewPortChanged
130+
return result
131+
}
126132
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import CoreLocation
2+
import Foundation
3+
4+
/// A representation of the MapView's current ViewPort.
5+
///
6+
/// This includes similar data to the MapViewCamera, but represents the raw
7+
/// values associated with the MapView. This information could used to prepare
8+
/// a new MapViewCamera on a scene, to cache the camera state, etc.
9+
public struct MapViewPort: Hashable, Equatable {
10+
/// The current center coordinate of the MapView
11+
public let center: CLLocationCoordinate2D
12+
13+
/// The current zoom value of the MapView
14+
public let zoom: Double
15+
16+
/// The current compass direction of the MapView
17+
public let direction: CLLocationDirection
18+
19+
public init(center: CLLocationCoordinate2D, zoom: Double, direction: CLLocationDirection) {
20+
self.center = center
21+
self.zoom = zoom
22+
self.direction = direction
23+
}
24+
25+
public static func zero(zoom: Double = 10) -> MapViewPort {
26+
MapViewPort(center: CLLocationCoordinate2D(latitude: 0, longitude: 0),
27+
zoom: zoom,
28+
direction: 0)
29+
}
30+
}
31+
32+
public extension MapViewPort {
33+
/// Generate a basic MapViewCamera that represents the MapViewPort
34+
///
35+
/// - Returns: The calculated MapViewCamera
36+
func asMapViewCamera() -> MapViewCamera {
37+
.center(center,
38+
zoom: zoom,
39+
direction: direction)
40+
}
41+
}

Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
1313
mapView = MapView(styleURL: URL(string: "https://maplibre.org")!)
1414
coordinator = MapView.Coordinator(parent: mapView) { _, _ in
1515
// No action
16+
} onViewPortChanged: { _ in
17+
// No action
1618
}
1719
}
1820

0 commit comments

Comments
 (0)