@lugg/maps 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LuggMaps.podspec +14 -1
- package/README.md +10 -0
- package/android/src/main/java/com/luggmaps/LuggMapView.kt +15 -2
- package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +4 -0
- package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +9 -0
- package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +25 -1
- package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +5 -0
- package/ios/LuggMapView.mm +81 -8
- package/ios/core/AppleMapProvider.mm +15 -16
- package/ios/core/AppleStaticMapProvider.mm +74 -0
- package/ios/core/GoogleMapProvider.mm +11 -10
- package/ios/core/GoogleStaticMapProvider.mm +20 -16
- package/ios/core/MapEdgeInsets.h +18 -0
- package/ios/core/MapProviderDelegate.h +4 -0
- package/ios/core/StaticMapProviderBase.h +5 -0
- package/ios/core/StaticMapProviderBase.mm +8 -6
- package/lib/module/MapView.js +5 -0
- package/lib/module/MapView.js.map +1 -1
- package/lib/module/MapView.web.js +20 -6
- package/lib/module/MapView.web.js.map +1 -1
- package/lib/module/fabric/LuggMapViewNativeComponent.ts +7 -1
- package/lib/typescript/plugin/src/index.d.ts +5 -0
- package/lib/typescript/plugin/src/index.d.ts.map +1 -1
- package/lib/typescript/plugin/src/withLuggMapsIOS.d.ts +1 -0
- package/lib/typescript/plugin/src/withLuggMapsIOS.d.ts.map +1 -1
- package/lib/typescript/src/MapView.d.ts +1 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -1
- package/lib/typescript/src/MapView.types.d.ts +9 -1
- package/lib/typescript/src/MapView.types.d.ts.map +1 -1
- package/lib/typescript/src/MapView.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +1 -0
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
- package/package.json +6 -3
- package/plugin/build/index.d.ts +5 -0
- package/plugin/build/index.js +5 -2
- package/plugin/build/withLuggMapsIOS.d.ts +1 -0
- package/plugin/build/withLuggMapsIOS.js +26 -4
- package/src/MapView.tsx +7 -0
- package/src/MapView.types.ts +9 -1
- package/src/MapView.web.tsx +24 -5
- package/src/fabric/LuggMapViewNativeComponent.ts +7 -1
package/LuggMaps.podspec
CHANGED
|
@@ -16,7 +16,20 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
17
|
s.private_header_files = "ios/**/*.h"
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# `$LuggMapsGoogleEnabled = false` in the Podfile drops the Google Maps SDK (Apple Maps only)
|
|
20
|
+
google_enabled = defined?($LuggMapsGoogleEnabled) ? $LuggMapsGoogleEnabled : true
|
|
21
|
+
s.pod_target_xcconfig = {
|
|
22
|
+
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) LUGG_GOOGLE_MAPS_ENABLED=#{google_enabled ? 1 : 0}"
|
|
23
|
+
}
|
|
24
|
+
if google_enabled
|
|
25
|
+
s.dependency "GoogleMaps"
|
|
26
|
+
else
|
|
27
|
+
unless defined?($LuggMapsGoogleDisabledLogged)
|
|
28
|
+
Pod::UI.puts "[LuggMaps] Google Maps SDK is disabled, Apple Maps only.".yellow
|
|
29
|
+
$LuggMapsGoogleDisabledLogged = true
|
|
30
|
+
end
|
|
31
|
+
s.exclude_files = "ios/core/Google*", "ios/core/GMS*"
|
|
32
|
+
end
|
|
20
33
|
s.frameworks = "MapKit"
|
|
21
34
|
|
|
22
35
|
install_modules_dependencies(s)
|
package/README.md
CHANGED
|
@@ -40,6 +40,16 @@ import { MapView, Marker } from '@lugg/maps';
|
|
|
40
40
|
|
|
41
41
|
Google Maps needs an API key on every platform. See [Installation](https://maps.lodev09.com/installation) for Expo, iOS, Android, and web setup.
|
|
42
42
|
|
|
43
|
+
## AI Skills
|
|
44
|
+
|
|
45
|
+
Skills give your AI coding agent working knowledge of `@lugg/maps` - setup, camera control, markers and callouts, static maps, advanced patterns, and platform limitations - so it generates correct code without you explaining the library each time.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npx skills add lugg/maps
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This installs the **Maps Usage** skill into your project. The source lives in [`skills/maps-usage`](skills/maps-usage).
|
|
52
|
+
|
|
43
53
|
## Contributing
|
|
44
54
|
|
|
45
55
|
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
@@ -131,7 +131,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
131
131
|
|
|
132
132
|
// region Provider Initialization
|
|
133
133
|
|
|
134
|
-
private fun initializeProvider() {
|
|
134
|
+
private fun initializeProvider(latitude: Double = initialLatitude, longitude: Double = initialLongitude, zoom: Float = initialZoom) {
|
|
135
135
|
if (provider != null || mapWrapperView == null) return
|
|
136
136
|
|
|
137
137
|
if (staticMode) {
|
|
@@ -146,7 +146,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
146
146
|
|
|
147
147
|
applyProps()
|
|
148
148
|
|
|
149
|
-
google.initializeMap(mapWrapperView!!,
|
|
149
|
+
google.initializeMap(mapWrapperView!!, latitude, longitude, zoom)
|
|
150
150
|
|
|
151
151
|
// Flush children mounted before provider was created
|
|
152
152
|
for (i in 0 until childCount) {
|
|
@@ -371,6 +371,19 @@ class LuggMapView(private val reactContext: ThemedReactContext) :
|
|
|
371
371
|
provider?.fitCoordinates(coordinates, edgeInsetsTop, edgeInsetsLeft, edgeInsetsBottom, edgeInsetsRight, duration)
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
fun reload() {
|
|
375
|
+
val current = provider ?: return
|
|
376
|
+
val latitude = current.cameraLatitude
|
|
377
|
+
val longitude = current.cameraLongitude
|
|
378
|
+
val zoom = current.cameraZoom
|
|
379
|
+
|
|
380
|
+
current.destroy()
|
|
381
|
+
provider = null
|
|
382
|
+
if (isAttachedToWindow) {
|
|
383
|
+
initializeProvider(latitude, longitude, zoom)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
374
387
|
// endregion
|
|
375
388
|
|
|
376
389
|
companion object {
|
|
@@ -2,6 +2,7 @@ package com.luggmaps
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.view.MotionEvent
|
|
5
|
+
import android.view.View
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
7
|
import com.facebook.react.views.view.ReactViewGroup
|
|
7
8
|
|
|
@@ -34,6 +35,14 @@ class LuggMapWrapperView(context: ThemedReactContext) : ReactViewGroup(context)
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
override fun onViewAdded(child: View) {
|
|
39
|
+
super.onViewAdded(child)
|
|
40
|
+
// A replacement map needs sizing even when Yoga's layout is unchanged.
|
|
41
|
+
if (indexOfChild(child) == 0) {
|
|
42
|
+
layoutChild(width, height)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
override fun onLayout(
|
|
38
47
|
changed: Boolean,
|
|
39
48
|
left: Int,
|
|
@@ -128,6 +128,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
128
128
|
|
|
129
129
|
// Theme
|
|
130
130
|
private var theme: String = "system"
|
|
131
|
+
private var mapType: String = "standard"
|
|
131
132
|
|
|
132
133
|
// Edge Insets
|
|
133
134
|
private var edgeInsets: EdgeInsets = EdgeInsets()
|
|
@@ -183,7 +184,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
183
184
|
view.onCreate(null)
|
|
184
185
|
view.onResume()
|
|
185
186
|
view.getMapAsync(this)
|
|
186
|
-
wrapper.addView(view)
|
|
187
|
+
wrapper.addView(view, 0)
|
|
187
188
|
}
|
|
188
189
|
wrapper.onLayoutReady = null
|
|
189
190
|
}
|
|
@@ -211,6 +212,9 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
211
212
|
groundOverlayToViewMap.clear()
|
|
212
213
|
markerToViewMap.clear()
|
|
213
214
|
wrapperView?.touchEventHandler = null
|
|
215
|
+
// The wrapper outlives the provider on reload; don't leave the dead map
|
|
216
|
+
// view in it
|
|
217
|
+
mapView?.let { wrapperView?.removeView(it) }
|
|
214
218
|
wrapperView = null
|
|
215
219
|
googleMap?.setOnCameraMoveStartedListener(null)
|
|
216
220
|
googleMap?.setOnCameraMoveListener(null)
|
|
@@ -276,6 +280,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
276
280
|
applyZoomLimits()
|
|
277
281
|
applyInsetAdjustment()
|
|
278
282
|
applyTheme()
|
|
283
|
+
setMapType(mapType)
|
|
279
284
|
applyUserLocation()
|
|
280
285
|
processPendingMarkers()
|
|
281
286
|
processPendingPolylines()
|
|
@@ -542,6 +547,7 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
542
547
|
}
|
|
543
548
|
|
|
544
549
|
override fun setMapType(value: String) {
|
|
550
|
+
mapType = value
|
|
545
551
|
googleMap?.mapType = when (value) {
|
|
546
552
|
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
|
|
547
553
|
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
|
|
@@ -1201,6 +1207,24 @@ class GoogleMapProvider(private val context: Context) :
|
|
|
1201
1207
|
|
|
1202
1208
|
// endregion
|
|
1203
1209
|
|
|
1210
|
+
// region Camera
|
|
1211
|
+
|
|
1212
|
+
// A static map's camera is inset-shifted (staticCameraTarget), so report
|
|
1213
|
+
// the requested camera instead
|
|
1214
|
+
private val liveCameraPosition: CameraPosition?
|
|
1215
|
+
get() = if (staticMode) null else googleMap?.cameraPosition
|
|
1216
|
+
|
|
1217
|
+
override val cameraLatitude: Double
|
|
1218
|
+
get() = liveCameraPosition?.target?.latitude ?: initialLatitude
|
|
1219
|
+
|
|
1220
|
+
override val cameraLongitude: Double
|
|
1221
|
+
get() = liveCameraPosition?.target?.longitude ?: initialLongitude
|
|
1222
|
+
|
|
1223
|
+
override val cameraZoom: Float
|
|
1224
|
+
get() = liveCameraPosition?.zoom ?: initialZoom
|
|
1225
|
+
|
|
1226
|
+
// endregion
|
|
1227
|
+
|
|
1204
1228
|
// region Lifecycle
|
|
1205
1229
|
|
|
1206
1230
|
override fun pauseAnimations() {
|
|
@@ -58,6 +58,11 @@ interface MapProvider {
|
|
|
58
58
|
fun addTileOverlayView(tileOverlayView: LuggTileOverlayView)
|
|
59
59
|
fun removeTileOverlayView(tileOverlayView: LuggTileOverlayView)
|
|
60
60
|
|
|
61
|
+
// Current camera (the initial camera until the map is ready)
|
|
62
|
+
val cameraLatitude: Double
|
|
63
|
+
val cameraLongitude: Double
|
|
64
|
+
val cameraZoom: Float
|
|
65
|
+
|
|
61
66
|
// Lifecycle
|
|
62
67
|
fun pauseAnimations()
|
|
63
68
|
fun resumeAnimations()
|
package/ios/LuggMapView.mm
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
#import "LuggTileOverlayView.h"
|
|
9
9
|
#import "core/AppleMapProvider.h"
|
|
10
10
|
#import "core/AppleStaticMapProvider.h"
|
|
11
|
+
#if LUGG_GOOGLE_MAPS_ENABLED
|
|
11
12
|
#import "core/GoogleMapProvider.h"
|
|
12
13
|
#import "core/GoogleStaticMapProvider.h"
|
|
14
|
+
#endif
|
|
13
15
|
#import "core/MapProviderDelegate.h"
|
|
14
16
|
#import "events/CameraIdleEvent.h"
|
|
15
17
|
#import "events/CameraMoveEvent.h"
|
|
@@ -311,15 +313,25 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
|
|
|
311
313
|
#pragma mark - Provider Initialization
|
|
312
314
|
|
|
313
315
|
- (void)initializeProvider {
|
|
314
|
-
if (_provider || !_mapWrapperView)
|
|
315
|
-
return;
|
|
316
|
-
|
|
317
316
|
const auto &viewProps =
|
|
318
317
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
318
|
+
[self
|
|
319
|
+
initializeProviderWithCoordinate:CLLocationCoordinate2DMake(
|
|
320
|
+
viewProps.initialCoordinate.latitude,
|
|
321
|
+
viewProps.initialCoordinate
|
|
322
|
+
.longitude)
|
|
323
|
+
zoom:viewProps.initialZoom];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
- (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate
|
|
327
|
+
zoom:(double)zoom {
|
|
328
|
+
if (_provider || !_mapWrapperView)
|
|
329
|
+
return;
|
|
319
330
|
|
|
320
331
|
if (_providerType == LuggMapViewProvider::Apple) {
|
|
321
332
|
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
|
|
322
333
|
: [[AppleMapProvider alloc] init];
|
|
334
|
+
#if LUGG_GOOGLE_MAPS_ENABLED
|
|
323
335
|
} else if (_staticMode) {
|
|
324
336
|
GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init];
|
|
325
337
|
google.mapId = _mapId;
|
|
@@ -329,17 +341,21 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
|
|
|
329
341
|
google.mapId = _mapId;
|
|
330
342
|
_provider = google;
|
|
331
343
|
}
|
|
344
|
+
#else
|
|
345
|
+
} else {
|
|
346
|
+
NSLog(@"[LuggMaps] provider=\"google\" requested but the Google Maps SDK "
|
|
347
|
+
@"is excluded ($LuggMapsGoogleEnabled = false); using Apple Maps");
|
|
348
|
+
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
|
|
349
|
+
: [[AppleMapProvider alloc] init];
|
|
350
|
+
}
|
|
351
|
+
#endif
|
|
332
352
|
|
|
333
353
|
_provider.delegate = self;
|
|
334
354
|
_provider.staticMode = _staticMode;
|
|
335
355
|
|
|
336
|
-
CLLocationCoordinate2D coordinate =
|
|
337
|
-
CLLocationCoordinate2DMake(viewProps.initialCoordinate.latitude,
|
|
338
|
-
viewProps.initialCoordinate.longitude);
|
|
339
|
-
|
|
340
356
|
[_provider initializeMapInView:_mapWrapperView
|
|
341
357
|
initialCoordinate:coordinate
|
|
342
|
-
initialZoom:
|
|
358
|
+
initialZoom:zoom];
|
|
343
359
|
|
|
344
360
|
// After initializeMapInView so the cache key reads the provider's camera;
|
|
345
361
|
// the base render is async and picks up the cached image
|
|
@@ -582,6 +598,63 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
|
|
|
582
598
|
duration:duration];
|
|
583
599
|
}
|
|
584
600
|
|
|
601
|
+
// Loads the map again, e.g. to recover from missing tiles. A static map
|
|
602
|
+
// drops its cached snapshot and renders the base map again in place.
|
|
603
|
+
// Neither SDK can reload a live map's tiles, so the native map view is
|
|
604
|
+
// recreated at the current camera (coordinate and zoom; heading and pitch
|
|
605
|
+
// reset) with children re-added.
|
|
606
|
+
- (void)reload {
|
|
607
|
+
if (_staticMode) {
|
|
608
|
+
_staticSnapshotDone = NO;
|
|
609
|
+
NSString *cacheKey = [self staticSnapshotCacheKey];
|
|
610
|
+
if (cacheKey) {
|
|
611
|
+
[StaticSnapshotCache() removeObjectForKey:cacheKey];
|
|
612
|
+
}
|
|
613
|
+
if ([_provider isKindOfClass:[StaticMapProviderBase class]]) {
|
|
614
|
+
[(StaticMapProviderBase *)_provider rerenderBaseMap];
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!_provider)
|
|
620
|
+
return;
|
|
621
|
+
|
|
622
|
+
CLLocationCoordinate2D coordinate = _provider.coordinate;
|
|
623
|
+
double zoom = _provider.zoom;
|
|
624
|
+
// Children keep their native marker/overlay objects from the old map;
|
|
625
|
+
// detach them so the new provider creates fresh ones on the flush
|
|
626
|
+
[self removeChildrenFromProvider];
|
|
627
|
+
[_provider destroy];
|
|
628
|
+
_provider = nil;
|
|
629
|
+
_initialized = NO;
|
|
630
|
+
[self initializeProviderWithCoordinate:coordinate zoom:zoom];
|
|
631
|
+
if ([_provider isKindOfClass:[AppleMapProvider class]]) {
|
|
632
|
+
// The captured camera already includes the previous inset offset.
|
|
633
|
+
[_provider moveCamera:coordinate.latitude
|
|
634
|
+
longitude:coordinate.longitude
|
|
635
|
+
zoom:zoom
|
|
636
|
+
duration:0];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
- (void)removeChildrenFromProvider {
|
|
641
|
+
for (UIView *subview in self.subviews) {
|
|
642
|
+
if ([subview isKindOfClass:[LuggMarkerView class]]) {
|
|
643
|
+
[_provider removeMarkerView:(LuggMarkerView *)subview];
|
|
644
|
+
} else if ([subview isKindOfClass:[LuggPolylineView class]]) {
|
|
645
|
+
[_provider removePolylineView:(LuggPolylineView *)subview];
|
|
646
|
+
} else if ([subview isKindOfClass:[LuggPolygonView class]]) {
|
|
647
|
+
[_provider removePolygonView:(LuggPolygonView *)subview];
|
|
648
|
+
} else if ([subview isKindOfClass:[LuggCircleView class]]) {
|
|
649
|
+
[_provider removeCircleView:(LuggCircleView *)subview];
|
|
650
|
+
} else if ([subview isKindOfClass:[LuggGroundOverlayView class]]) {
|
|
651
|
+
[_provider removeGroundOverlayView:(LuggGroundOverlayView *)subview];
|
|
652
|
+
} else if ([subview isKindOfClass:[LuggTileOverlayView class]]) {
|
|
653
|
+
[_provider removeTileOverlayView:(LuggTileOverlayView *)subview];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
585
658
|
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
586
659
|
RCTLuggMapViewHandleCommand(self, commandName, args);
|
|
587
660
|
}
|
|
@@ -15,6 +15,7 @@ using facebook::react::LuggMapViewTheme;
|
|
|
15
15
|
#import "../extensions/MKMapView+Zoom.h"
|
|
16
16
|
#import "LuggAnnotationView.h"
|
|
17
17
|
#import "MKPolylineAnimator.h"
|
|
18
|
+
#import "MapEdgeInsets.h"
|
|
18
19
|
|
|
19
20
|
@interface AppleMarkerAnnotation : NSObject <MKAnnotation>
|
|
20
21
|
@property(nonatomic, assign) CLLocationCoordinate2D coordinate;
|
|
@@ -221,6 +222,14 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
221
222
|
_isMapReady = NO;
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
- (CLLocationCoordinate2D)coordinate {
|
|
226
|
+
return _mapView.centerCoordinate;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
- (double)zoom {
|
|
230
|
+
return _mapView.zoomLevel;
|
|
231
|
+
}
|
|
232
|
+
|
|
224
233
|
- (void)destroyMapView {
|
|
225
234
|
[self dismissNonBubbledCallout];
|
|
226
235
|
[self stopEdgeInsetsAnimation];
|
|
@@ -457,13 +466,11 @@ MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
|
|
|
457
466
|
|
|
458
467
|
- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
|
|
459
468
|
oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
CGFloat newOffsetX = (edgeInsets.left - edgeInsets.right) / 2.0;
|
|
463
|
-
CGFloat newOffsetY = (edgeInsets.top - edgeInsets.bottom) / 2.0;
|
|
469
|
+
CGPoint oldOffset = LuggMapInsetOffset(oldEdgeInsets);
|
|
470
|
+
CGPoint newOffset = LuggMapInsetOffset(edgeInsets);
|
|
464
471
|
|
|
465
|
-
CGFloat deltaX =
|
|
466
|
-
CGFloat deltaY =
|
|
472
|
+
CGFloat deltaX = newOffset.x - oldOffset.x;
|
|
473
|
+
CGFloat deltaY = newOffset.y - oldOffset.y;
|
|
467
474
|
|
|
468
475
|
_mapView.layoutMargins = edgeInsets;
|
|
469
476
|
_edgeInsetsCurrent = edgeInsets;
|
|
@@ -506,16 +513,8 @@ MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
|
|
|
506
513
|
CFTimeInterval elapsed = CACurrentMediaTime() - _edgeInsetsAnimationStart;
|
|
507
514
|
CGFloat progress = MIN(elapsed / _edgeInsetsAnimationDuration, 1.0);
|
|
508
515
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
UIEdgeInsets current = UIEdgeInsetsMake(
|
|
513
|
-
_edgeInsetsFrom.top + (_edgeInsetsTo.top - _edgeInsetsFrom.top) * t,
|
|
514
|
-
_edgeInsetsFrom.left + (_edgeInsetsTo.left - _edgeInsetsFrom.left) * t,
|
|
515
|
-
_edgeInsetsFrom.bottom +
|
|
516
|
-
(_edgeInsetsTo.bottom - _edgeInsetsFrom.bottom) * t,
|
|
517
|
-
_edgeInsetsFrom.right +
|
|
518
|
-
(_edgeInsetsTo.right - _edgeInsetsFrom.right) * t);
|
|
516
|
+
UIEdgeInsets current =
|
|
517
|
+
LuggMapInsetsAtProgress(_edgeInsetsFrom, _edgeInsetsTo, progress);
|
|
519
518
|
|
|
520
519
|
[self setEdgeInsets:current oldEdgeInsets:_edgeInsetsCurrent];
|
|
521
520
|
|
|
@@ -4,6 +4,7 @@ using facebook::react::LuggMapViewPoiFilterMode;
|
|
|
4
4
|
using facebook::react::LuggMapViewTheme;
|
|
5
5
|
|
|
6
6
|
#import "../extensions/MKMapView+Zoom.h"
|
|
7
|
+
#import "../extensions/UIView+Snapshot.h"
|
|
7
8
|
#import "AppleMapProvider.h"
|
|
8
9
|
#import "LuggAnnotationView.h"
|
|
9
10
|
|
|
@@ -42,8 +43,13 @@ static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
|
|
|
42
43
|
return rect;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
@interface AppleStaticMapProvider () <MKMapViewDelegate>
|
|
47
|
+
@end
|
|
48
|
+
|
|
45
49
|
@implementation AppleStaticMapProvider {
|
|
46
50
|
MKMapSnapshotter *_snapshotter;
|
|
51
|
+
MKMapView *_warmupMapView;
|
|
52
|
+
BOOL _snapshotReady;
|
|
47
53
|
|
|
48
54
|
BOOL _poiEnabled;
|
|
49
55
|
LuggMapViewPoiFilterMode _poiFilterMode;
|
|
@@ -106,6 +112,12 @@ static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
|
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
- (void)renderBaseMap {
|
|
115
|
+
// MKMapSnapshotter bakes attribution into the image without inset control.
|
|
116
|
+
if (!UIEdgeInsetsEqualToEdgeInsets(self.edgeInsets, UIEdgeInsetsZero)) {
|
|
117
|
+
[self renderInsetBaseMap];
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
if (_snapshotter)
|
|
110
122
|
return;
|
|
111
123
|
|
|
@@ -140,6 +152,68 @@ static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
|
|
|
140
152
|
- (void)cancelBaseRender {
|
|
141
153
|
[_snapshotter cancel];
|
|
142
154
|
_snapshotter = nil;
|
|
155
|
+
_warmupMapView.delegate = nil;
|
|
156
|
+
[_warmupMapView removeFromSuperview];
|
|
157
|
+
_warmupMapView = nil;
|
|
158
|
+
_snapshotReady = NO;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#pragma mark - Inset snapshots
|
|
162
|
+
|
|
163
|
+
- (void)renderInsetBaseMap {
|
|
164
|
+
if (_warmupMapView) {
|
|
165
|
+
if (!_snapshotReady || !_warmupMapView.window)
|
|
166
|
+
return;
|
|
167
|
+
|
|
168
|
+
UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
|
|
169
|
+
if (!imageView)
|
|
170
|
+
return;
|
|
171
|
+
|
|
172
|
+
[self displayBaseImage:imageView.image fromCache:NO];
|
|
173
|
+
[self cancelBaseRender];
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
MKMapView *mapView =
|
|
178
|
+
[[MKMapView alloc] initWithFrame:self.wrapperView.bounds];
|
|
179
|
+
mapView.userInteractionEnabled = NO;
|
|
180
|
+
mapView.showsCompass = NO;
|
|
181
|
+
mapView.insetsLayoutMarginsFromSafeArea = NO;
|
|
182
|
+
mapView.layoutMargins = self.edgeInsets;
|
|
183
|
+
mapView.mapType = LuggMKMapTypeFromMapType(self.mapType);
|
|
184
|
+
mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
|
|
185
|
+
_poiEnabled, _poiFilterMode, _poiFilterCategories);
|
|
186
|
+
mapView.overrideUserInterfaceStyle =
|
|
187
|
+
[self snapshotTraitCollection].userInterfaceStyle;
|
|
188
|
+
[mapView setVisibleMapRect:self.mapRect
|
|
189
|
+
edgePadding:UIEdgeInsetsZero
|
|
190
|
+
animated:NO];
|
|
191
|
+
_warmupMapView = mapView;
|
|
192
|
+
mapView.delegate = self;
|
|
193
|
+
[self.wrapperView insertSubview:mapView atIndex:0];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)mapViewWillStartRenderingMap:(MKMapView *)mapView {
|
|
197
|
+
_snapshotReady = NO;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView
|
|
201
|
+
fullyRendered:(BOOL)fullyRendered {
|
|
202
|
+
_snapshotReady = fullyRendered;
|
|
203
|
+
[self revealOverlays];
|
|
204
|
+
if (!fullyRendered)
|
|
205
|
+
return;
|
|
206
|
+
|
|
207
|
+
__weak AppleStaticMapProvider *weakSelf = self;
|
|
208
|
+
__weak MKMapView *weakMapView = mapView;
|
|
209
|
+
[[NSRunLoop mainRunLoop]
|
|
210
|
+
performInModes:@[ NSDefaultRunLoopMode ]
|
|
211
|
+
block:^{
|
|
212
|
+
AppleStaticMapProvider *strongSelf = weakSelf;
|
|
213
|
+
if (strongSelf && weakMapView &&
|
|
214
|
+
strongSelf->_warmupMapView == weakMapView)
|
|
215
|
+
[strongSelf renderInsetBaseMap];
|
|
216
|
+
}];
|
|
143
217
|
}
|
|
144
218
|
|
|
145
219
|
#pragma mark - Props
|
|
@@ -13,6 +13,7 @@ using facebook::react::LuggMapViewTheme;
|
|
|
13
13
|
#import "../LuggPolylineView.h"
|
|
14
14
|
#import "../LuggTileOverlayView.h"
|
|
15
15
|
#import "GMSPolylineAnimator.h"
|
|
16
|
+
#import "MapEdgeInsets.h"
|
|
16
17
|
#import "PolylineAnimatorBase.h"
|
|
17
18
|
|
|
18
19
|
static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
@@ -183,6 +184,14 @@ LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme) {
|
|
|
183
184
|
_mapView = nil;
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
- (CLLocationCoordinate2D)coordinate {
|
|
188
|
+
return _mapView.camera.target;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
- (double)zoom {
|
|
192
|
+
return _mapView.camera.zoom;
|
|
193
|
+
}
|
|
194
|
+
|
|
186
195
|
#pragma mark - Props
|
|
187
196
|
|
|
188
197
|
- (void)setZoomEnabled:(BOOL)enabled {
|
|
@@ -288,16 +297,8 @@ LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme) {
|
|
|
288
297
|
CFTimeInterval elapsed = CACurrentMediaTime() - _edgeInsetsAnimationStart;
|
|
289
298
|
CGFloat progress = MIN(elapsed / _edgeInsetsAnimationDuration, 1.0);
|
|
290
299
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
UIEdgeInsets current = UIEdgeInsetsMake(
|
|
295
|
-
_edgeInsetsFrom.top + (_edgeInsetsTo.top - _edgeInsetsFrom.top) * t,
|
|
296
|
-
_edgeInsetsFrom.left + (_edgeInsetsTo.left - _edgeInsetsFrom.left) * t,
|
|
297
|
-
_edgeInsetsFrom.bottom +
|
|
298
|
-
(_edgeInsetsTo.bottom - _edgeInsetsFrom.bottom) * t,
|
|
299
|
-
_edgeInsetsFrom.right +
|
|
300
|
-
(_edgeInsetsTo.right - _edgeInsetsFrom.right) * t);
|
|
300
|
+
UIEdgeInsets current =
|
|
301
|
+
LuggMapInsetsAtProgress(_edgeInsetsFrom, _edgeInsetsTo, progress);
|
|
301
302
|
|
|
302
303
|
_mapView.padding = current;
|
|
303
304
|
|
|
@@ -49,7 +49,7 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
|
49
49
|
|
|
50
50
|
@implementation GoogleStaticMapProvider {
|
|
51
51
|
GMSMapView *_warmupMapView;
|
|
52
|
-
BOOL
|
|
52
|
+
BOOL _snapshotReady;
|
|
53
53
|
BOOL _swapScheduled;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -106,7 +106,7 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
|
106
106
|
if (!wrapperView)
|
|
107
107
|
return;
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
_snapshotReady = NO;
|
|
110
110
|
|
|
111
111
|
GMSMapView *mapView = DequeueStaticMapView([self poolKey]);
|
|
112
112
|
if (mapView) {
|
|
@@ -152,12 +152,12 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
|
152
152
|
_warmupMapView = nil;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// Once
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
155
|
+
// Once the map is stable, swap the live map with its image and release the
|
|
156
|
+
// map view. The image render and map teardown are expensive, so they run
|
|
157
|
+
// outside scroll tracking; the live map keeps displaying (and loading
|
|
158
|
+
// tiles) until then.
|
|
159
159
|
- (void)scheduleSwap {
|
|
160
|
-
if (_swapScheduled || !_warmupMapView || !
|
|
160
|
+
if (_swapScheduled || !_warmupMapView || !_snapshotReady ||
|
|
161
161
|
!_warmupMapView.window)
|
|
162
162
|
return;
|
|
163
163
|
|
|
@@ -176,9 +176,9 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
|
176
176
|
|
|
177
177
|
- (void)performSwap {
|
|
178
178
|
// Tiles can invalidate (or the view leave the window) between scheduling
|
|
179
|
-
// and this runloop pass; the next
|
|
179
|
+
// and this runloop pass; the next snapshotReady or resumeAnimations
|
|
180
180
|
// retries
|
|
181
|
-
if (!_warmupMapView || !
|
|
181
|
+
if (!_warmupMapView || !_snapshotReady || !_warmupMapView.window)
|
|
182
182
|
return;
|
|
183
183
|
|
|
184
184
|
UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
|
|
@@ -192,20 +192,24 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
|
192
192
|
#pragma mark - GMSMapViewDelegate
|
|
193
193
|
|
|
194
194
|
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
|
|
195
|
-
|
|
195
|
+
_snapshotReady = NO;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// Fires for tiles that failed permanently too, so it can't gate the swap:
|
|
199
|
+
// capturing here cached blank tiles under the static key with no way to
|
|
200
|
+
// recover. The visible warmup map has rendered what it can; show the
|
|
201
|
+
// overlays now instead of waiting for the swap, which is deferred while
|
|
202
|
+
// scrolling
|
|
198
203
|
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
|
|
199
|
-
_tilesRendered = YES;
|
|
200
|
-
// The visible warmup map is fully rendered; show the overlays now
|
|
201
|
-
// instead of waiting for the swap, which is deferred while scrolling
|
|
202
204
|
[self revealOverlays];
|
|
203
|
-
[self scheduleSwap];
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
// Stable per the SDK: tiles loaded, labels and overlays rendered. The only
|
|
208
|
+
// signal that the render is complete, so the only one that captures and
|
|
209
|
+
// caches. Without it (e.g. offline) the live map stays until the view
|
|
210
|
+
// leaves the window, and a fresh warmup runs on return.
|
|
206
211
|
- (void)mapViewSnapshotReady:(GMSMapView *)mapView {
|
|
207
|
-
|
|
208
|
-
_tilesRendered = YES;
|
|
212
|
+
_snapshotReady = YES;
|
|
209
213
|
[self revealOverlays];
|
|
210
214
|
[self scheduleSwap];
|
|
211
215
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
static inline CGPoint LuggMapInsetOffset(UIEdgeInsets insets) {
|
|
6
|
+
return CGPointMake((insets.left - insets.right) / 2.0,
|
|
7
|
+
(insets.top - insets.bottom) / 2.0);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static inline UIEdgeInsets
|
|
11
|
+
LuggMapInsetsAtProgress(UIEdgeInsets from, UIEdgeInsets to, CGFloat progress) {
|
|
12
|
+
// Ease out cubic
|
|
13
|
+
CGFloat t = 1.0 - (1.0 - progress) * (1.0 - progress) * (1.0 - progress);
|
|
14
|
+
return UIEdgeInsetsMake(from.top + (to.top - from.top) * t,
|
|
15
|
+
from.left + (to.left - from.left) * t,
|
|
16
|
+
from.bottom + (to.bottom - from.bottom) * t,
|
|
17
|
+
from.right + (to.right - from.right) * t);
|
|
18
|
+
}
|
|
@@ -89,6 +89,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
89
89
|
- (void)removeTileOverlayView:(LuggTileOverlayView *)tileOverlayView;
|
|
90
90
|
- (void)syncTileOverlayView:(LuggTileOverlayView *)tileOverlayView;
|
|
91
91
|
|
|
92
|
+
// Current camera (the initial camera until the map is ready)
|
|
93
|
+
@property(nonatomic, readonly) CLLocationCoordinate2D coordinate;
|
|
94
|
+
@property(nonatomic, readonly) double zoom;
|
|
95
|
+
|
|
92
96
|
// Lifecycle
|
|
93
97
|
- (void)pauseAnimations;
|
|
94
98
|
- (void)resumeAnimations;
|
|
@@ -42,6 +42,11 @@ CGPoint LuggStaticPointForCoordinate(MKMapRect mapRect, CGSize size,
|
|
|
42
42
|
/// delegate callbacks and marks the base render done.
|
|
43
43
|
- (void)displayBaseImage:(UIImage *)image fromCache:(BOOL)fromCache;
|
|
44
44
|
|
|
45
|
+
/// Discards any cached, displayed or in-flight base render and renders
|
|
46
|
+
/// again at the current camera. The displayed image stays until the new
|
|
47
|
+
/// one replaces it.
|
|
48
|
+
- (void)rerenderBaseMap;
|
|
49
|
+
|
|
45
50
|
/// Shows the marker and shape overlays (idempotent). Called automatically
|
|
46
51
|
/// when the base image displays; subclasses whose base map is already
|
|
47
52
|
/// visible earlier (e.g. a live warmup map) can call it sooner.
|