Skip to content

Commit f0f2666

Browse files
hactarianthetechie
andauthored
Add support for MapViewProxy updates in realtime (#76)
* - adding support for realtime proxy updates - improved documentation * linting * linting * updating changelog * update test * Update CHANGELOG.md --------- Co-authored-by: Ian Wagner <[email protected]>
1 parent ac5f240 commit f0f2666

File tree

6 files changed

+78
-21
lines changed

6 files changed

+78
-21
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Version 0.8.0 - 2025-03-17
9+
10+
- onMapViewProxyUpdate can now be set up to update in realtime or when animations/scrolling complete (default).
11+
812
## Version 0.7.0 - 2025-02-02
913

1014
### Added

Package.resolved

Lines changed: 23 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/MapLibreSwiftUI/MapView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentab
1717

1818
var onStyleLoaded: ((MLNStyle) -> Void)?
1919
var onViewProxyChanged: ((MapViewProxy) -> Void)?
20+
var proxyUpdateMode: ProxyUpdateMode?
2021

2122
var mapViewContentInset: UIEdgeInsets?
2223

@@ -50,7 +51,8 @@ public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentab
5051
MapViewCoordinator<T>(
5152
parent: self,
5253
onGesture: { processGesture($0, $1) },
53-
onViewProxyChanged: { onViewProxyChanged?($0) }
54+
onViewProxyChanged: { onViewProxyChanged?($0) },
55+
proxyUpdateMode: proxyUpdateMode ?? .onFinish
5456
)
5557
}
5658

Sources/MapLibreSwiftUI/MapViewCoordinator.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import MapLibre
33
import MapLibreSwiftDSL
44

5-
public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapViewDelegate {
5+
public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, @preconcurrency MLNMapViewDelegate {
66
// This must be weak, the UIViewRepresentable owns the MLNMapView.
77
weak var mapView: MLNMapView?
88
var parent: MapView<T>
@@ -21,14 +21,17 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
2121
var onStyleLoaded: ((MLNStyle) -> Void)?
2222
var onGesture: (MLNMapView, UIGestureRecognizer) -> Void
2323
var onViewProxyChanged: (MapViewProxy) -> Void
24+
var proxyUpdateMode: ProxyUpdateMode
2425

2526
init(parent: MapView<T>,
2627
onGesture: @escaping (MLNMapView, UIGestureRecognizer) -> Void,
27-
onViewProxyChanged: @escaping (MapViewProxy) -> Void)
28+
onViewProxyChanged: @escaping (MapViewProxy) -> Void,
29+
proxyUpdateMode: ProxyUpdateMode)
2830
{
2931
self.parent = parent
3032
self.onGesture = onGesture
3133
self.onViewProxyChanged = onViewProxyChanged
34+
self.proxyUpdateMode = proxyUpdateMode
3235
}
3336

3437
// MARK: Core UIView Functionality
@@ -376,6 +379,8 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
376379
public func mapView(_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool) {
377380
// TODO: We could put this in regionIsChangingWith if we calculate significant change/debounce.
378381
MainActor.assumeIsolated {
382+
// regionIsChangingWith is not called for the final update, so we need to call updateViewProxy
383+
// in both modes here.
379384
updateViewProxy(mapView: mapView, reason: reason)
380385

381386
guard !suppressCameraUpdatePropagation else {
@@ -386,6 +391,13 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
386391
}
387392
}
388393

394+
@MainActor
395+
public func mapView(_ mapView: MLNMapView, regionIsChangingWith reason: MLNCameraChangeReason) {
396+
if proxyUpdateMode == .realtime {
397+
updateViewProxy(mapView: mapView, reason: reason)
398+
}
399+
}
400+
389401
// MARK: MapViewProxy
390402

391403
@MainActor private func updateViewProxy(mapView: MLNMapView, reason: MLNCameraChangeReason) {
@@ -396,3 +408,12 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
396408
onViewProxyChanged(calculatedViewProxy)
397409
}
398410
}
411+
412+
public enum ProxyUpdateMode {
413+
/// Causes the `MapViewProxy`to be updated in realtime, including during map view scrolling and animations.
414+
/// This will cause multiple updates per seconds. Use only if you really need to run code in realtime while users
415+
/// are changing the shown region.
416+
case realtime
417+
/// Default. Causes `MapViewProxy` to be only be updated when a map view scroll or animation completes.
418+
case onFinish
419+
}

Sources/MapLibreSwiftUI/MapViewModifiers.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,30 @@ public extension MapView {
130130
return result
131131
}
132132

133-
func onMapViewProxyUpdate(_ onViewProxyChanged: @escaping (MapViewProxy) -> Void) -> Self {
133+
/// The view modifier recieves an instance of `MapViewProxy`, which contains read only information about the current
134+
/// state of the
135+
/// `MapView` such as its bounds, center and insets.
136+
/// - Parameters:
137+
/// - updateMode: How frequently the `MapViewProxy` is updated. Per default this is set to `.onFinish`, so updates
138+
/// are only sent when the map finally completes updating due to animations or scrolling. Can be set to `.realtime`
139+
/// to recieve updates during the animations and scrolling too.
140+
/// - onViewProxyChanged: The closure containing the `MapViewProxy`. Use this to run code based on the current
141+
/// mapView state.
142+
///
143+
/// Example:
144+
/// ```swift
145+
/// .onMapViewProxyUpdate() { proxy in
146+
/// print("The map zoom level is: \(proxy.zoomLevel)")
147+
/// }
148+
/// ```
149+
///
150+
func onMapViewProxyUpdate(
151+
updateMode: ProxyUpdateMode = .onFinish,
152+
onViewProxyChanged: @escaping (MapViewProxy) -> Void
153+
) -> Self {
134154
var result = self
135155
result.onViewProxyChanged = onViewProxyChanged
156+
result.proxyUpdateMode = updateMode
136157
return result
137158
}
138159

Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ final class MapViewCoordinatorCameraTests: XCTestCase {
1313
maplibreMapView = MockMLNMapViewCameraUpdating()
1414
given(maplibreMapView).frame.willReturn(.zero)
1515
mapView = MapView(styleURL: URL(string: "https://maplibre.org")!)
16-
coordinator = MapView.Coordinator(parent: mapView) { _, _ in
16+
coordinator = MapView.Coordinator(parent: mapView, onGesture: { _, _ in
1717
// No action
18-
} onViewProxyChanged: { _ in
18+
}, onViewProxyChanged: { _ in
1919
// No action
20-
}
20+
}, proxyUpdateMode: .onFinish)
2121
}
2222

2323
@MainActor func testUnchangedCamera() {

0 commit comments

Comments
 (0)