@lugg/maps 0.2.0-alpha.2 → 0.2.0-alpha.20

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.
Files changed (96) hide show
  1. package/README.md +17 -4
  2. package/android/src/main/java/com/luggmaps/LuggGoogleMapView.kt +31 -37
  3. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +6 -5
  4. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +136 -14
  5. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +21 -0
  6. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +16 -0
  7. package/android/src/main/java/com/luggmaps/LuggPolylineViewManager.kt +22 -0
  8. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +157 -52
  9. package/ios/LuggAppleMapView.mm +154 -42
  10. package/ios/LuggGoogleMapView.mm +52 -22
  11. package/ios/LuggMarkerView.h +9 -0
  12. package/ios/LuggMarkerView.mm +79 -0
  13. package/ios/LuggPolylineView.h +4 -0
  14. package/ios/LuggPolylineView.mm +23 -0
  15. package/ios/core/GMSPolylineAnimator.h +3 -0
  16. package/ios/core/GMSPolylineAnimator.m +164 -41
  17. package/ios/core/MKPolylineAnimator.h +4 -0
  18. package/ios/core/MKPolylineAnimator.m +162 -43
  19. package/ios/core/PolylineAnimatorBase.h +14 -0
  20. package/ios/core/PolylineAnimatorBase.m +33 -0
  21. package/ios/extensions/MKMapView+Zoom.h +2 -0
  22. package/ios/extensions/MKMapView+Zoom.m +14 -4
  23. package/lib/module/MapProvider.js +13 -0
  24. package/lib/module/MapProvider.js.map +1 -0
  25. package/lib/module/MapProvider.types.js +4 -0
  26. package/lib/module/MapProvider.types.js.map +1 -0
  27. package/lib/module/MapProvider.web.js +20 -0
  28. package/lib/module/MapProvider.web.js.map +1 -0
  29. package/lib/module/MapView.js +2 -2
  30. package/lib/module/MapView.js.map +1 -1
  31. package/lib/module/MapView.web.js +272 -0
  32. package/lib/module/MapView.web.js.map +1 -0
  33. package/lib/module/components/Marker.js +10 -1
  34. package/lib/module/components/Marker.js.map +1 -1
  35. package/lib/module/components/Marker.web.js +33 -0
  36. package/lib/module/components/Marker.web.js.map +1 -0
  37. package/lib/module/components/Polyline.js +8 -3
  38. package/lib/module/components/Polyline.js.map +1 -1
  39. package/lib/module/components/Polyline.web.js +229 -0
  40. package/lib/module/components/Polyline.web.js.map +1 -0
  41. package/lib/module/components/index.js +2 -2
  42. package/lib/module/components/index.js.map +1 -1
  43. package/lib/module/components/index.web.js +5 -0
  44. package/lib/module/components/index.web.js.map +1 -0
  45. package/lib/module/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  46. package/lib/module/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  47. package/lib/module/index.js +3 -2
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/index.web.js +6 -0
  50. package/lib/module/index.web.js.map +1 -0
  51. package/lib/typescript/src/MapProvider.d.ts +8 -0
  52. package/lib/typescript/src/MapProvider.d.ts.map +1 -0
  53. package/lib/typescript/src/MapProvider.types.d.ts +16 -0
  54. package/lib/typescript/src/MapProvider.types.d.ts.map +1 -0
  55. package/lib/typescript/src/MapProvider.web.d.ts +11 -0
  56. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -0
  57. package/lib/typescript/src/MapView.d.ts +1 -1
  58. package/lib/typescript/src/MapView.d.ts.map +1 -1
  59. package/lib/typescript/src/MapView.types.d.ts +2 -2
  60. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  61. package/lib/typescript/src/MapView.web.d.ts +3 -0
  62. package/lib/typescript/src/MapView.web.d.ts.map +1 -0
  63. package/lib/typescript/src/components/Marker.d.ts +21 -0
  64. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  65. package/lib/typescript/src/components/Marker.web.d.ts +3 -0
  66. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Polyline.d.ts +32 -0
  68. package/lib/typescript/src/components/Polyline.d.ts.map +1 -1
  69. package/lib/typescript/src/components/Polyline.web.d.ts +3 -0
  70. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -0
  71. package/lib/typescript/src/components/index.web.d.ts +5 -0
  72. package/lib/typescript/src/components/index.web.d.ts.map +1 -0
  73. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts +4 -1
  74. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts.map +1 -1
  75. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts +7 -0
  76. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts.map +1 -1
  77. package/lib/typescript/src/index.d.ts +3 -1
  78. package/lib/typescript/src/index.d.ts.map +1 -1
  79. package/lib/typescript/src/index.web.d.ts +7 -0
  80. package/lib/typescript/src/index.web.d.ts.map +1 -0
  81. package/package.json +15 -2
  82. package/src/MapProvider.tsx +10 -0
  83. package/src/MapProvider.types.ts +16 -0
  84. package/src/MapProvider.web.tsx +14 -0
  85. package/src/MapView.tsx +2 -2
  86. package/src/MapView.types.ts +2 -2
  87. package/src/MapView.web.tsx +337 -0
  88. package/src/components/Marker.tsx +37 -3
  89. package/src/components/Marker.web.tsx +33 -0
  90. package/src/components/Polyline.tsx +38 -1
  91. package/src/components/Polyline.web.tsx +287 -0
  92. package/src/components/index.web.ts +4 -0
  93. package/src/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  94. package/src/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  95. package/src/index.ts +8 -1
  96. package/src/index.web.ts +17 -0
@@ -89,6 +89,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
89
89
  marker.map = nil;
90
90
  markerView.marker = nil;
91
91
  }
92
+ [markerView resetIconViewTransform];
92
93
  } else if ([childComponentView isKindOfClass:[LuggPolylineView class]]) {
93
94
  LuggPolylineView *polylineView = (LuggPolylineView *)childComponentView;
94
95
  [_polylineAnimators removeObjectForKey:polylineView];
@@ -189,6 +190,11 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
189
190
 
190
191
  - (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
191
192
  _isDragging = gesture;
193
+ if (_isDragging) {
194
+ for (GMSPolylineAnimator *animator in _polylineAnimators.objectEnumerator) {
195
+ [animator pause];
196
+ }
197
+ }
192
198
  }
193
199
 
194
200
  - (void)mapView:(GMSMapView *)mapView
@@ -202,6 +208,11 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
202
208
  idleAtCameraPosition:(GMSCameraPosition *)position {
203
209
  BOOL wasDragging = _isDragging;
204
210
  _isDragging = NO;
211
+ if (wasDragging) {
212
+ for (GMSPolylineAnimator *animator in _polylineAnimators.objectEnumerator) {
213
+ [animator resume];
214
+ }
215
+ }
205
216
  CameraIdleEvent{position.target.latitude, position.target.longitude,
206
217
  position.zoom, static_cast<bool>(wasDragging)}
207
218
  .emit<LuggGoogleMapViewEventEmitter>(_eventEmitter);
@@ -225,6 +236,35 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
225
236
 
226
237
  #pragma mark - Marker Management
227
238
 
239
+ - (void)applyMarkerStyle:(LuggMarkerView *)markerView
240
+ marker:(GMSAdvancedMarker *)marker {
241
+ if (markerView.hasCustomView) {
242
+ if (markerView.rasterize) {
243
+ marker.iconView = nil;
244
+ marker.icon = [markerView createScaledIconImage];
245
+ marker.rotation = markerView.rotate;
246
+ } else {
247
+ UIView *iconView = markerView.iconView;
248
+ if (marker.iconView != iconView) {
249
+ [iconView removeFromSuperview];
250
+ marker.iconView = iconView;
251
+ }
252
+ CGFloat scale = markerView.scale;
253
+ CGFloat radians = markerView.rotate * M_PI / 180.0;
254
+ iconView.transform =
255
+ CGAffineTransformConcat(CGAffineTransformMakeScale(scale, scale),
256
+ CGAffineTransformMakeRotation(radians));
257
+ marker.rotation = 0;
258
+ }
259
+ marker.groundAnchor = markerView.anchor;
260
+ } else {
261
+ marker.iconView = nil;
262
+ marker.icon = nil;
263
+ marker.rotation = markerView.rotate;
264
+ marker.groundAnchor = CGPointMake(0.5, 1);
265
+ }
266
+ }
267
+
228
268
  - (void)syncMarkerView:(LuggMarkerView *)markerView caller:(NSString *)caller {
229
269
  if (!_mapView) {
230
270
  if (![_pendingMarkerViews containsObject:markerView]) {
@@ -242,15 +282,8 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
242
282
  marker.position = markerView.coordinate;
243
283
  marker.title = markerView.title;
244
284
  marker.snippet = markerView.markerDescription;
245
- marker.groundAnchor = markerView.anchor;
246
-
247
- if (markerView.hasCustomView) {
248
- UIView *iconView = markerView.iconView;
249
- [iconView removeFromSuperview];
250
- marker.iconView = iconView;
251
- } else {
252
- marker.iconView = nil;
253
- }
285
+ marker.zIndex = (int)markerView.zIndex;
286
+ [self applyMarkerStyle:markerView marker:marker];
254
287
  }
255
288
 
256
289
  - (void)processPendingMarkers {
@@ -270,22 +303,15 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
270
303
  return;
271
304
  }
272
305
 
273
- UIView *iconView = markerView.iconView;
274
- [iconView removeFromSuperview];
275
-
276
306
  GMSAdvancedMarker *marker = [[GMSAdvancedMarker alloc] init];
277
307
  marker.position = markerView.coordinate;
278
308
  marker.title = markerView.title;
279
309
  marker.snippet = markerView.markerDescription;
280
- marker.collisionBehavior = GMSCollisionBehaviorRequired;
310
+ marker.zIndex = (int)markerView.zIndex;
281
311
 
282
- if (markerView.hasCustomView) {
283
- marker.iconView = iconView;
284
- }
312
+ [self applyMarkerStyle:markerView marker:marker];
285
313
 
286
- marker.groundAnchor = markerView.anchor;
287
314
  marker.map = _mapView;
288
-
289
315
  markerView.marker = marker;
290
316
  }
291
317
 
@@ -306,12 +332,14 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
306
332
 
307
333
  GMSPolyline *polyline = (GMSPolyline *)polylineView.polyline;
308
334
  polyline.strokeWidth = polylineView.strokeWidth;
335
+ polyline.zIndex = (int)polylineView.zIndex;
309
336
 
310
337
  GMSPolylineAnimator *animator =
311
338
  [_polylineAnimators objectForKey:polylineView];
312
339
  if (animator) {
313
340
  animator.coordinates = polylineView.coordinates;
314
341
  animator.strokeColors = polylineView.strokeColors;
342
+ animator.animatedOptions = polylineView.animatedOptions;
315
343
  animator.animated = polylineView.animated;
316
344
  [animator update];
317
345
  }
@@ -335,6 +363,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
335
363
 
336
364
  GMSPolyline *polyline = [GMSPolyline polylineWithPath:[GMSMutablePath path]];
337
365
  polyline.strokeWidth = polylineView.strokeWidth;
366
+ polyline.zIndex = (int)polylineView.zIndex;
338
367
  polyline.map = _mapView;
339
368
  polylineView.polyline = polyline;
340
369
 
@@ -342,6 +371,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
342
371
  animator.polyline = polyline;
343
372
  animator.coordinates = polylineView.coordinates;
344
373
  animator.strokeColors = polylineView.strokeColors;
374
+ animator.animatedOptions = polylineView.animatedOptions;
345
375
  animator.animated = polylineView.animated;
346
376
  [animator update];
347
377
 
@@ -393,10 +423,10 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
393
423
  return;
394
424
  }
395
425
 
396
- GMSCameraPosition *camera =
397
- [GMSCameraPosition cameraWithLatitude:latitude
398
- longitude:longitude
399
- zoom:(float)zoom];
426
+ float targetZoom = zoom > 0 ? (float)zoom : _mapView.camera.zoom;
427
+ GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:latitude
428
+ longitude:longitude
429
+ zoom:targetZoom];
400
430
  if (duration < 0) {
401
431
  [_mapView animateToCameraPosition:camera];
402
432
  } else if (duration > 0) {
@@ -14,16 +14,25 @@ NS_ASSUME_NONNULL_BEGIN
14
14
 
15
15
  @interface LuggMarkerView : RCTViewComponentView
16
16
 
17
+ @property(nonatomic, readonly, nullable) NSString *name;
17
18
  @property(nonatomic, readonly) CLLocationCoordinate2D coordinate;
18
19
  @property(nonatomic, readonly, nullable) NSString *title;
19
20
  @property(nonatomic, readonly, nullable) NSString *markerDescription;
20
21
  @property(nonatomic, readonly) CGPoint anchor;
22
+ @property(nonatomic, readonly) NSInteger zIndex;
23
+ @property(nonatomic, readonly) CLLocationDegrees rotate;
24
+ @property(nonatomic, readonly) CGFloat scale;
25
+ @property(nonatomic, readonly) BOOL rasterize;
21
26
  @property(nonatomic, readonly) BOOL hasCustomView;
22
27
  @property(nonatomic, readonly) BOOL didLayout;
23
28
  @property(nonatomic, readonly) UIView *iconView;
24
29
  @property(nonatomic, weak, nullable) id<LuggMarkerViewDelegate> delegate;
25
30
  @property(nonatomic, strong, nullable) NSObject *marker;
26
31
 
32
+ - (nullable UIImage *)createIconImage;
33
+ - (nullable UIImage *)createScaledIconImage;
34
+ - (void)resetIconViewTransform;
35
+
27
36
  @end
28
37
 
29
38
  NS_ASSUME_NONNULL_END
@@ -13,10 +13,15 @@ using namespace facebook::react;
13
13
  @end
14
14
 
15
15
  @implementation LuggMarkerView {
16
+ NSString *_name;
16
17
  CLLocationCoordinate2D _coordinate;
17
18
  NSString *_title;
18
19
  NSString *_markerDescription;
19
20
  CGPoint _anchor;
21
+ NSInteger _zIndex;
22
+ CLLocationDegrees _rotate;
23
+ CGFloat _scale;
24
+ BOOL _rasterize;
20
25
  BOOL _didLayout;
21
26
  UIView *_iconView;
22
27
  }
@@ -34,6 +39,10 @@ using namespace facebook::react;
34
39
 
35
40
  _coordinate = CLLocationCoordinate2DMake(0, 0);
36
41
  _anchor = CGPointMake(0.5, 1.0);
42
+ _zIndex = 0;
43
+ _rotate = 0;
44
+ _scale = 1;
45
+ _rasterize = YES;
37
46
  _didLayout = NO;
38
47
 
39
48
  _iconView = [[UIView alloc] init];
@@ -52,12 +61,17 @@ using namespace facebook::react;
52
61
  const auto &newViewProps =
53
62
  *std::static_pointer_cast<LuggMarkerViewProps const>(props);
54
63
 
64
+ _name = [NSString stringWithUTF8String:newViewProps.name.c_str()];
55
65
  _coordinate = CLLocationCoordinate2DMake(newViewProps.coordinate.latitude,
56
66
  newViewProps.coordinate.longitude);
57
67
  _title = [NSString stringWithUTF8String:newViewProps.title.c_str()];
58
68
  _markerDescription =
59
69
  [NSString stringWithUTF8String:newViewProps.description.c_str()];
60
70
  _anchor = CGPointMake(newViewProps.anchor.x, newViewProps.anchor.y);
71
+ _zIndex = newViewProps.zIndex.value_or(0);
72
+ _rotate = newViewProps.rotate;
73
+ _scale = newViewProps.scale;
74
+ _rasterize = newViewProps.rasterize;
61
75
  }
62
76
 
63
77
  - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
@@ -109,6 +123,10 @@ using namespace facebook::react;
109
123
  }
110
124
  }
111
125
 
126
+ - (NSString *)name {
127
+ return _name;
128
+ }
129
+
112
130
  - (CLLocationCoordinate2D)coordinate {
113
131
  return _coordinate;
114
132
  }
@@ -125,6 +143,22 @@ using namespace facebook::react;
125
143
  return _anchor;
126
144
  }
127
145
 
146
+ - (NSInteger)zIndex {
147
+ return _zIndex;
148
+ }
149
+
150
+ - (CLLocationDegrees)rotate {
151
+ return _rotate;
152
+ }
153
+
154
+ - (CGFloat)scale {
155
+ return _scale;
156
+ }
157
+
158
+ - (BOOL)rasterize {
159
+ return _rasterize;
160
+ }
161
+
128
162
  - (BOOL)hasCustomView {
129
163
  return _iconView.subviews.count > 0;
130
164
  }
@@ -137,11 +171,56 @@ using namespace facebook::react;
137
171
  return _iconView;
138
172
  }
139
173
 
174
+ - (UIImage *)createIconImage {
175
+ CGSize size = _iconView.bounds.size;
176
+ if (size.width <= 0 || size.height <= 0) {
177
+ return nil;
178
+ }
179
+
180
+ UIGraphicsImageRendererFormat *format =
181
+ [UIGraphicsImageRendererFormat defaultFormat];
182
+ format.scale = [UIScreen mainScreen].scale;
183
+ UIGraphicsImageRenderer *renderer =
184
+ [[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
185
+
186
+ return [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
187
+ [self->_iconView.layer renderInContext:context.CGContext];
188
+ }];
189
+ }
190
+
191
+ - (UIImage *)createScaledIconImage {
192
+ CGSize size = _iconView.bounds.size;
193
+ if (size.width <= 0 || size.height <= 0) {
194
+ return nil;
195
+ }
196
+
197
+ CGSize scaledSize = CGSizeMake(size.width * _scale, size.height * _scale);
198
+
199
+ UIGraphicsImageRendererFormat *format =
200
+ [UIGraphicsImageRendererFormat defaultFormat];
201
+ format.scale = [UIScreen mainScreen].scale;
202
+ UIGraphicsImageRenderer *renderer =
203
+ [[UIGraphicsImageRenderer alloc] initWithSize:scaledSize format:format];
204
+
205
+ return [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
206
+ CGContextScaleCTM(context.CGContext, self->_scale, self->_scale);
207
+ [self->_iconView.layer renderInContext:context.CGContext];
208
+ }];
209
+ }
210
+
211
+ - (void)resetIconViewTransform {
212
+ _iconView.transform = CGAffineTransformIdentity;
213
+ _iconView.layer.anchorPoint = CGPointMake(0.5, 0.5);
214
+ _iconView.frame = CGRectMake(0, 0, _iconView.bounds.size.width,
215
+ _iconView.bounds.size.height);
216
+ }
217
+
140
218
  - (void)prepareForRecycle {
141
219
  [super prepareForRecycle];
142
220
  _didLayout = NO;
143
221
  self.marker = nil;
144
222
  self.delegate = nil;
223
+ [self resetIconViewTransform];
145
224
  for (UIView *subview in _iconView.subviews) {
146
225
  [subview removeFromSuperview];
147
226
  }
@@ -1,3 +1,4 @@
1
+ #import "core/PolylineAnimatorBase.h"
1
2
  #import <CoreLocation/CoreLocation.h>
2
3
  #import <React/RCTViewComponentView.h>
3
4
  #import <UIKit/UIKit.h>
@@ -16,7 +17,10 @@ NS_ASSUME_NONNULL_BEGIN
16
17
  @property(nonatomic, readonly) NSArray<CLLocation *> *coordinates;
17
18
  @property(nonatomic, readonly) NSArray<UIColor *> *strokeColors;
18
19
  @property(nonatomic, readonly) BOOL animated;
20
+ @property(nonatomic, readonly, nullable)
21
+ PolylineAnimatedOptions *animatedOptions;
19
22
  @property(nonatomic, readonly) CGFloat strokeWidth;
23
+ @property(nonatomic, readonly) NSInteger zIndex;
20
24
  @property(nonatomic, weak, nullable) id<LuggPolylineViewDelegate> delegate;
21
25
  @property(nonatomic, strong, nullable) NSObject *polyline;
22
26
  @property(nonatomic, weak, nullable) NSObject *renderer;
@@ -17,7 +17,9 @@ using namespace facebook::react;
17
17
  NSArray<CLLocation *> *_coordinates;
18
18
  NSArray<UIColor *> *_strokeColors;
19
19
  BOOL _animated;
20
+ PolylineAnimatedOptions *_animatedOptions;
20
21
  CGFloat _strokeWidth;
22
+ NSInteger _zIndex;
21
23
  }
22
24
 
23
25
  + (ComponentDescriptorProvider)componentDescriptorProvider {
@@ -72,7 +74,20 @@ using namespace facebook::react;
72
74
 
73
75
  _animated = newViewProps.animated;
74
76
 
77
+ const auto &opts = newViewProps.animatedOptions;
78
+ PolylineAnimatedOptions *options = [[PolylineAnimatedOptions alloc] init];
79
+ options.duration = opts.duration > 0 ? opts.duration : 2150;
80
+ options.trailLength = (opts.trailLength > 0 && opts.trailLength <= 1.0)
81
+ ? opts.trailLength
82
+ : 1.0;
83
+ options.delay = opts.delay;
84
+ options.easing = !opts.easing.empty()
85
+ ? [NSString stringWithUTF8String:opts.easing.c_str()]
86
+ : @"linear";
87
+ _animatedOptions = options;
88
+
75
89
  _strokeWidth = newViewProps.strokeWidth > 0 ? newViewProps.strokeWidth : 1.0;
90
+ _zIndex = newViewProps.zIndex.value_or(0);
76
91
  }
77
92
 
78
93
  - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
@@ -97,10 +112,18 @@ using namespace facebook::react;
97
112
  return _animated;
98
113
  }
99
114
 
115
+ - (PolylineAnimatedOptions *)animatedOptions {
116
+ return _animatedOptions;
117
+ }
118
+
100
119
  - (CGFloat)strokeWidth {
101
120
  return _strokeWidth;
102
121
  }
103
122
 
123
+ - (NSInteger)zIndex {
124
+ return _zIndex;
125
+ }
126
+
104
127
  - (void)prepareForRecycle {
105
128
  [super prepareForRecycle];
106
129
  self.polyline = nil;
@@ -5,7 +5,10 @@
5
5
 
6
6
  @property(nonatomic, weak) GMSPolyline *polyline;
7
7
  @property(nonatomic, assign) BOOL animated;
8
+ @property(nonatomic, strong) PolylineAnimatedOptions *animatedOptions;
8
9
 
9
10
  - (void)update;
11
+ - (void)pause;
12
+ - (void)resume;
10
13
 
11
14
  @end
@@ -1,15 +1,53 @@
1
1
  #import "GMSPolylineAnimator.h"
2
2
  #import <QuartzCore/QuartzCore.h>
3
3
 
4
+ @interface GMSDisplayLinkProxy : NSObject
5
+ @property(nonatomic, weak) id target;
6
+ @property(nonatomic, assign) SEL selector;
7
+ @end
8
+
9
+ @implementation GMSDisplayLinkProxy
10
+ - (instancetype)initWithTarget:(id)target selector:(SEL)selector {
11
+ if (self = [super init]) {
12
+ _target = target;
13
+ _selector = selector;
14
+ }
15
+ return self;
16
+ }
17
+ - (void)tick:(CADisplayLink *)displayLink {
18
+ if (_target) {
19
+ #pragma clang diagnostic push
20
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
21
+ [_target performSelector:_selector withObject:displayLink];
22
+ #pragma clang diagnostic pop
23
+ } else {
24
+ [displayLink invalidate];
25
+ }
26
+ }
27
+ @end
28
+
4
29
  @implementation GMSPolylineAnimator {
5
30
  CADisplayLink *_displayLink;
31
+ GMSDisplayLinkProxy *_displayLinkProxy;
6
32
  CGFloat _animationProgress;
33
+ CGFloat _delayRemaining;
34
+ NSArray<NSNumber *> *_cumulativeDistances;
35
+ CGFloat _totalLength;
7
36
  }
8
37
 
38
+ @synthesize animatedOptions = _animatedOptions;
39
+
9
40
  - (void)dealloc {
10
41
  [self stopAnimation];
11
42
  }
12
43
 
44
+ - (void)setCoordinates:(NSArray<CLLocation *> *)coordinates {
45
+ [super setCoordinates:coordinates];
46
+ if (_animated && _displayLink) {
47
+ [self computeCumulativeDistances];
48
+ }
49
+ }
50
+
13
51
  - (void)setAnimated:(BOOL)animated {
14
52
  if (_animated == animated) {
15
53
  return;
@@ -24,26 +62,74 @@
24
62
  }
25
63
  }
26
64
 
65
+ - (void)setAnimatedOptions:(PolylineAnimatedOptions *)animatedOptions {
66
+ _animatedOptions = animatedOptions ?: [PolylineAnimatedOptions defaultOptions];
67
+ if (_animated && _displayLink) {
68
+ [self stopAnimation];
69
+ [self startAnimation];
70
+ }
71
+ }
72
+
27
73
  - (void)startAnimation {
28
74
  if (_displayLink) {
29
75
  return;
30
76
  }
77
+ [self computeCumulativeDistances];
31
78
  _animationProgress = 0;
32
- _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationTick:)];
79
+ _delayRemaining = self.animatedOptions.delay / 1000.0;
80
+ _displayLinkProxy = [[GMSDisplayLinkProxy alloc] initWithTarget:self selector:@selector(animationTick:)];
81
+ _displayLink = [CADisplayLink displayLinkWithTarget:_displayLinkProxy selector:@selector(tick:)];
33
82
  [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
34
83
  }
35
84
 
85
+ - (void)computeCumulativeDistances {
86
+ NSMutableArray<NSNumber *> *distances = [NSMutableArray array];
87
+ CGFloat total = 0;
88
+ [distances addObject:@(0)];
89
+
90
+ for (NSUInteger i = 1; i < self.coordinates.count; i++) {
91
+ CLLocation *prev = self.coordinates[i - 1];
92
+ CLLocation *curr = self.coordinates[i];
93
+ total += [prev distanceFromLocation:curr];
94
+ [distances addObject:@(total)];
95
+ }
96
+
97
+ _cumulativeDistances = [distances copy];
98
+ _totalLength = total;
99
+ }
100
+
36
101
  - (void)stopAnimation {
37
102
  [_displayLink invalidate];
38
103
  _displayLink = nil;
104
+ _displayLinkProxy = nil;
105
+ }
106
+
107
+ - (void)pause {
108
+ _displayLink.paused = YES;
109
+ }
110
+
111
+ - (void)resume {
112
+ _displayLink.paused = NO;
39
113
  }
40
114
 
41
115
  - (void)animationTick:(CADisplayLink *)displayLink {
42
- CGFloat speed = displayLink.duration / 1.75;
116
+ if (_delayRemaining > 0) {
117
+ _delayRemaining -= displayLink.duration;
118
+ return;
119
+ }
120
+
121
+ CGFloat duration = self.animatedOptions.duration / 1000.0;
122
+ if (duration <= 0) duration = 2.15;
123
+
124
+ CGFloat trailLength = MAX(0.01, MIN(1.0, self.animatedOptions.trailLength));
125
+ CGFloat maxProgress = (trailLength < 1.0) ? 1.0 : 2.15;
126
+
127
+ CGFloat speed = displayLink.duration / duration;
43
128
  _animationProgress += speed;
44
129
 
45
- if (_animationProgress >= 2.15) {
130
+ if (_animationProgress >= maxProgress) {
46
131
  _animationProgress = 0;
132
+ _delayRemaining = self.animatedOptions.delay / 1000.0;
47
133
  }
48
134
 
49
135
  [self updateAnimatedPolyline];
@@ -71,63 +157,100 @@
71
157
  }
72
158
  }
73
159
 
160
+ - (NSUInteger)indexForDistance:(CGFloat)distance {
161
+ NSUInteger left = 0;
162
+ NSUInteger right = _cumulativeDistances.count - 1;
163
+
164
+ while (left < right) {
165
+ NSUInteger mid = (left + right + 1) / 2;
166
+ if (_cumulativeDistances[mid].doubleValue <= distance) {
167
+ left = mid;
168
+ } else {
169
+ right = mid - 1;
170
+ }
171
+ }
172
+
173
+ return MIN(left, _cumulativeDistances.count - 2);
174
+ }
175
+
176
+ - (CLLocationCoordinate2D)coordinateAtDistance:(CGFloat)distance {
177
+ if (distance <= 0) {
178
+ return self.coordinates.firstObject.coordinate;
179
+ }
180
+ if (distance >= _totalLength) {
181
+ return self.coordinates.lastObject.coordinate;
182
+ }
183
+
184
+ NSUInteger idx = [self indexForDistance:distance];
185
+ CGFloat segStart = _cumulativeDistances[idx].doubleValue;
186
+ CGFloat segEnd = _cumulativeDistances[idx + 1].doubleValue;
187
+ CGFloat segLength = segEnd - segStart;
188
+
189
+ CGFloat t = (segLength > 0) ? (distance - segStart) / segLength : 0;
190
+ CLLocationCoordinate2D c1 = self.coordinates[idx].coordinate;
191
+ CLLocationCoordinate2D c2 = self.coordinates[idx + 1].coordinate;
192
+
193
+ return CLLocationCoordinate2DMake(c1.latitude + (c2.latitude - c1.latitude) * t,
194
+ c1.longitude + (c2.longitude - c1.longitude) * t);
195
+ }
196
+
74
197
  - (void)updateAnimatedPolyline {
75
- if (!_polyline || self.coordinates.count < 2) {
198
+ if (!_polyline || self.coordinates.count < 2 || _totalLength <= 0) {
76
199
  return;
77
200
  }
78
201
 
79
- NSUInteger segmentCount = self.coordinates.count - 1;
80
- CGFloat progress = MIN(_animationProgress, 2.0);
81
- CGFloat headPos, tailPos;
202
+ CGFloat trailLength = MAX(0.01, MIN(1.0, self.animatedOptions.trailLength));
203
+ CGFloat maxProgress = (trailLength < 1.0) ? 1.0 : 2.0;
204
+ CGFloat progress = MIN(_animationProgress, maxProgress);
205
+ CGFloat easedProgress = [self applyEasing:progress / maxProgress] * maxProgress;
206
+
207
+ CGFloat headDist, tailDist;
82
208
 
83
- if (progress <= 1.0) {
84
- tailPos = 0;
85
- headPos = progress * segmentCount;
209
+ if (trailLength < 1.0) {
210
+ headDist = easedProgress * _totalLength;
211
+ tailDist = MAX(0, headDist - _totalLength * trailLength);
212
+ } else if (easedProgress <= 1.0) {
213
+ tailDist = 0;
214
+ headDist = easedProgress * _totalLength;
86
215
  } else {
87
- CGFloat shrinkProgress = progress - 1.0;
88
- tailPos = shrinkProgress * segmentCount;
89
- headPos = segmentCount;
216
+ CGFloat shrinkProgress = easedProgress - 1.0;
217
+ tailDist = shrinkProgress * _totalLength;
218
+ headDist = _totalLength;
90
219
  }
91
220
 
92
- if (headPos <= tailPos) {
221
+ if (headDist <= tailDist) {
93
222
  _polyline.path = [GMSMutablePath path];
94
223
  return;
95
224
  }
96
225
 
97
- NSUInteger startIndex = (NSUInteger)floor(tailPos);
98
- NSUInteger endIndex = (NSUInteger)ceil(headPos);
99
- CGFloat visibleLength = headPos - tailPos;
226
+ CGFloat visibleLength = headDist - tailDist;
227
+ NSUInteger startIndex = [self indexForDistance:tailDist];
228
+ NSUInteger endIndex = [self indexForDistance:headDist];
100
229
 
101
230
  GMSMutablePath *path = [GMSMutablePath path];
102
231
  NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
103
232
 
104
- for (NSUInteger i = startIndex; i <= endIndex && i < self.coordinates.count; i++) {
105
- CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
106
-
107
- if (i == startIndex && tailPos > (CGFloat)startIndex) {
108
- CGFloat t = tailPos - (CGFloat)startIndex;
109
- CLLocationCoordinate2D nextCoord = self.coordinates[i + 1].coordinate;
110
- coord.latitude = coord.latitude + (nextCoord.latitude - coord.latitude) * t;
111
- coord.longitude = coord.longitude + (nextCoord.longitude - coord.longitude) * t;
112
- }
233
+ CLLocationCoordinate2D startCoord = [self coordinateAtDistance:tailDist];
234
+ [path addCoordinate:startCoord];
113
235
 
114
- if (i == endIndex && headPos < (CGFloat)endIndex && i > 0) {
115
- CGFloat t = headPos - (CGFloat)(endIndex - 1);
116
- CLLocationCoordinate2D prevCoord = self.coordinates[i - 1].coordinate;
117
- coord.latitude = prevCoord.latitude + (coord.latitude - prevCoord.latitude) * t;
118
- coord.longitude = prevCoord.longitude + (coord.longitude - prevCoord.longitude) * t;
119
- }
236
+ for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
237
+ [path addCoordinate:self.coordinates[i].coordinate];
238
+ }
120
239
 
121
- [path addCoordinate:coord];
240
+ CLLocationCoordinate2D endCoord = [self coordinateAtDistance:headDist];
241
+ CLLocationCoordinate2D lastAdded =
242
+ (endIndex < self.coordinates.count) ? self.coordinates[endIndex].coordinate : endCoord;
243
+ if (endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
244
+ [path addCoordinate:endCoord];
245
+ }
122
246
 
123
- if (i < endIndex && i < segmentCount) {
124
- CGFloat segStartPos = MAX((CGFloat)i, tailPos);
125
- CGFloat segEndPos = MIN((CGFloat)(i + 1), headPos);
126
- CGFloat gradientMid = ((segStartPos + segEndPos) / 2.0 - tailPos) / visibleLength;
127
- UIColor *color = [self colorAtGradientPosition:gradientMid];
128
- GMSStrokeStyle *style = [GMSStrokeStyle solidColor:color];
129
- [spans addObject:[GMSStyleSpan spanWithStyle:style]];
130
- }
247
+ NSUInteger pathCount = path.count;
248
+ for (NSUInteger i = 0; i < pathCount - 1; i++) {
249
+ CGFloat segMidDist = tailDist + visibleLength * ((CGFloat)i + 0.5) / (CGFloat)(pathCount - 1);
250
+ CGFloat gradientPos = (segMidDist - tailDist) / visibleLength;
251
+ UIColor *color = [self colorAtGradientPosition:gradientPos];
252
+ GMSStrokeStyle *style = [GMSStrokeStyle solidColor:color];
253
+ [spans addObject:[GMSStyleSpan spanWithStyle:style]];
131
254
  }
132
255
 
133
256
  _polyline.path = path;
@@ -1,3 +1,4 @@
1
+ #import "PolylineAnimatorBase.h"
1
2
  #import <MapKit/MapKit.h>
2
3
 
3
4
  @interface MKPolylineAnimator : MKOverlayPathRenderer
@@ -6,7 +7,10 @@
6
7
 
7
8
  @property(nonatomic, strong) NSArray<UIColor *> *strokeColors;
8
9
  @property(nonatomic, assign) BOOL animated;
10
+ @property(nonatomic, strong) PolylineAnimatedOptions *animatedOptions;
9
11
 
10
12
  - (void)updatePolyline:(MKPolyline *)polyline;
13
+ - (void)pause;
14
+ - (void)resume;
11
15
 
12
16
  @end