@maplibre/maplibre-react-native 11.0.0-alpha.37 → 11.0.0-alpha.38

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 (57) hide show
  1. package/android/src/main/java/org/maplibre/reactnative/components/images/MLRNImages.kt +172 -0
  2. package/android/src/main/java/org/maplibre/reactnative/components/images/MLRNImagesManager.kt +73 -0
  3. package/android/src/main/java/org/maplibre/reactnative/components/layers/style/MLRNStyle.java +1 -1
  4. package/android/src/main/java/org/maplibre/reactnative/components/layers/style/MLRNStyleValue.java +2 -2
  5. package/android/src/main/java/org/maplibre/reactnative/components/mapview/MLRNMapView.kt +2 -1
  6. package/android/src/main/java/org/maplibre/reactnative/events/constants/EventKeys.java +0 -3
  7. package/android/src/main/java/org/maplibre/reactnative/utils/DownloadMapImageTask.java +6 -4
  8. package/android/src/main/java/org/maplibre/reactnative/utils/ImageEntry.kt +14 -0
  9. package/ios/components/images/MLRNImageQueueOperation.m +22 -17
  10. package/ios/components/images/MLRNImages.h +12 -7
  11. package/ios/components/images/MLRNImages.m +104 -39
  12. package/ios/components/images/MLRNImagesComponentView.h +9 -0
  13. package/ios/components/images/MLRNImagesComponentView.mm +107 -0
  14. package/ios/components/map-view/MLRNMapViewComponentView.mm +1 -8
  15. package/ios/components/sources/geojson-source/MLRNGeoJSONSourceComponentView.mm +4 -1
  16. package/ios/modules/network/MLRNNetworkHTTPHeaders.h +4 -4
  17. package/lib/commonjs/components/images/Images.js +48 -0
  18. package/lib/commonjs/components/images/Images.js.map +1 -0
  19. package/lib/commonjs/components/images/ImagesNativeComponent.ts +27 -0
  20. package/lib/commonjs/index.js +1 -1
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/module/components/images/Images.js +42 -0
  23. package/lib/module/components/images/Images.js.map +1 -0
  24. package/lib/module/components/images/ImagesNativeComponent.ts +27 -0
  25. package/lib/module/index.js +1 -1
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/typescript/commonjs/src/components/images/Images.d.ts +61 -0
  28. package/lib/typescript/commonjs/src/components/images/Images.d.ts.map +1 -0
  29. package/lib/typescript/commonjs/src/components/images/ImagesNativeComponent.d.ts +17 -0
  30. package/lib/typescript/commonjs/src/components/images/ImagesNativeComponent.d.ts.map +1 -0
  31. package/lib/typescript/commonjs/src/index.d.ts +1 -1
  32. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  33. package/lib/typescript/module/src/components/images/Images.d.ts +61 -0
  34. package/lib/typescript/module/src/components/images/Images.d.ts.map +1 -0
  35. package/lib/typescript/module/src/components/images/ImagesNativeComponent.d.ts +17 -0
  36. package/lib/typescript/module/src/components/images/ImagesNativeComponent.d.ts.map +1 -0
  37. package/lib/typescript/module/src/index.d.ts +1 -1
  38. package/lib/typescript/module/src/index.d.ts.map +1 -1
  39. package/package.json +2 -1
  40. package/src/components/images/Images.tsx +103 -0
  41. package/src/components/images/ImagesNativeComponent.ts +27 -0
  42. package/src/index.ts +6 -1
  43. package/android/src/main/java/org/maplibre/reactnative/components/images/MLRNImages.java +0 -233
  44. package/android/src/main/java/org/maplibre/reactnative/components/images/MLRNImagesManager.java +0 -103
  45. package/android/src/main/java/org/maplibre/reactnative/events/ImageMissingEvent.java +0 -42
  46. package/android/src/main/java/org/maplibre/reactnative/utils/ImageEntry.java +0 -26
  47. package/ios/components/images/MLRNImagesManager.h +0 -5
  48. package/ios/components/images/MLRNImagesManager.m +0 -20
  49. package/lib/commonjs/components/Images.js +0 -62
  50. package/lib/commonjs/components/Images.js.map +0 -1
  51. package/lib/module/components/Images.js +0 -57
  52. package/lib/module/components/Images.js.map +0 -1
  53. package/lib/typescript/commonjs/src/components/Images.d.ts +0 -41
  54. package/lib/typescript/commonjs/src/components/Images.d.ts.map +0 -1
  55. package/lib/typescript/module/src/components/Images.d.ts +0 -41
  56. package/lib/typescript/module/src/components/Images.d.ts.map +0 -1
  57. package/src/components/Images.tsx +0 -118
@@ -0,0 +1,172 @@
1
+ package org.maplibre.reactnative.components.images
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import androidx.core.content.res.ResourcesCompat
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.ReactContext
8
+ import com.facebook.react.bridge.WritableMap
9
+ import com.facebook.react.uimanager.UIManagerHelper
10
+ import com.facebook.react.uimanager.events.Event
11
+ import com.facebook.react.uimanager.events.EventDispatcher
12
+ import org.maplibre.android.maps.MapLibreMap
13
+ import org.maplibre.android.utils.BitmapUtils
14
+ import org.maplibre.reactnative.R
15
+ import org.maplibre.reactnative.components.AbstractMapFeature
16
+ import org.maplibre.reactnative.components.mapview.MLRNMapView
17
+ import org.maplibre.reactnative.utils.DownloadMapImageTask
18
+ import org.maplibre.reactnative.utils.ImageEntry
19
+
20
+ class MLRNImages(
21
+ context: Context,
22
+ ) : AbstractMapFeature(context) {
23
+ companion object {
24
+ private var imagePlaceholder: Bitmap? = null
25
+ }
26
+
27
+ private val currentImages = mutableSetOf<String>()
28
+ private val images = mutableMapOf<String, ImageEntry>()
29
+ private var map: MapLibreMap? = null
30
+
31
+ init {
32
+ if (imagePlaceholder == null) {
33
+ imagePlaceholder =
34
+ BitmapUtils.getBitmapFromDrawable(
35
+ ResourcesCompat.getDrawable(context.resources, R.drawable.empty_drawable, null),
36
+ )
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Set unified images.
42
+ * ImageEntry.uri can be:
43
+ * - A native asset name (simple name like "pin")
44
+ * - A URL (starts with http/https/file/asset/data or /)
45
+ */
46
+ fun setImages(
47
+ imagesList: List<Map.Entry<String, ImageEntry>>,
48
+ context: Context,
49
+ ) {
50
+ val newImages = mutableMapOf<String, ImageEntry>()
51
+
52
+ for (entry in imagesList) {
53
+ val key = entry.key
54
+ val value = entry.value
55
+ val oldValue = images.put(key, value)
56
+ if (oldValue == null) {
57
+ newImages[key] = value
58
+ }
59
+ }
60
+
61
+ map?.let { mapInstance ->
62
+ if (mapInstance.style != null) {
63
+ processImages(newImages, mapInstance, context)
64
+ }
65
+ }
66
+ }
67
+
68
+ override fun removeFromMap(mapView: MLRNMapView) {
69
+ removeImages(mapView)
70
+ map = null
71
+ images.clear()
72
+ currentImages.clear()
73
+ }
74
+
75
+ private fun removeImages(mapView: MLRNMapView) {
76
+ mapView.getStyle { style ->
77
+ for (imageName in images.keys) {
78
+ style.removeImage(imageName)
79
+ }
80
+ }
81
+ }
82
+
83
+ override fun addToMap(mapView: MLRNMapView) {
84
+ // Wait for style before adding the source to the map.
85
+ // Only then we can pre-load required images/placeholders into the style.
86
+ mapView.getStyle { _ ->
87
+ val mapInstance = mapView.mapLibreMap
88
+ map = mapInstance
89
+ map?.let {
90
+ processImages(images, it, context)
91
+ }
92
+ }
93
+ }
94
+
95
+ private fun processImages(
96
+ imagesToProcess: Map<String, ImageEntry>,
97
+ mapInstance: MapLibreMap,
98
+ context: Context,
99
+ ) {
100
+ if (imagesToProcess.isEmpty()) return
101
+
102
+ val style = mapInstance.style ?: return
103
+ val remoteImages = mutableListOf<Map.Entry<String, ImageEntry>>()
104
+
105
+ for ((imageName, entry) in imagesToProcess) {
106
+ if (hasImage(imageName, mapInstance)) continue
107
+
108
+ style.addImage(imageName, imagePlaceholder!!)
109
+ remoteImages.add(java.util.AbstractMap.SimpleEntry(imageName, entry))
110
+ currentImages.add(imageName)
111
+ }
112
+
113
+ // Download remote images asynchronously
114
+ if (remoteImages.isNotEmpty()) {
115
+ val task = DownloadMapImageTask(context, mapInstance, null)
116
+ task.execute(*remoteImages.toTypedArray())
117
+ }
118
+ }
119
+
120
+ fun addMissingImageToStyle(
121
+ id: String,
122
+ mapInstance: MapLibreMap,
123
+ ): Boolean {
124
+ val entry = images[id] ?: return false
125
+ val style = mapInstance.style ?: return false
126
+
127
+ style.addImage(id, imagePlaceholder!!)
128
+ val task = DownloadMapImageTask(context, mapInstance, null)
129
+ task.execute(java.util.AbstractMap.SimpleEntry(id, entry))
130
+
131
+ currentImages.add(id)
132
+ return true
133
+ }
134
+
135
+ val eventDispatcher: EventDispatcher?
136
+ get() {
137
+ val reactContext = context as ReactContext
138
+
139
+ return UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
140
+ }
141
+
142
+ val surfaceId: Int
143
+ get() {
144
+ val reactContext = context as ReactContext
145
+
146
+ return UIManagerHelper.getSurfaceId(reactContext)
147
+ }
148
+
149
+ inner class OnImageMissingEvent(
150
+ private val eventData: WritableMap,
151
+ ) : Event<OnImageMissingEvent>(this@MLRNImages.surfaceId, this@MLRNImages.id) {
152
+ override fun getEventName() = "onImageMissing"
153
+
154
+ override fun getEventData() = eventData
155
+ }
156
+
157
+ fun sendImageMissingEvent(image: String) {
158
+ val writableMap = Arguments.createMap()
159
+ writableMap.putString("image", image)
160
+
161
+ eventDispatcher?.dispatchEvent(OnImageMissingEvent(writableMap))
162
+ }
163
+
164
+ private fun hasImage(
165
+ imageId: String,
166
+ mapInstance: MapLibreMap,
167
+ ): Boolean {
168
+ val style = mapInstance.style
169
+
170
+ return style?.getImage(imageId) != null
171
+ }
172
+ }
@@ -0,0 +1,73 @@
1
+ package org.maplibre.reactnative.components.images
2
+
3
+ import com.facebook.react.bridge.Dynamic
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReadableType
6
+ import com.facebook.react.module.annotations.ReactModule
7
+ import com.facebook.react.uimanager.ThemedReactContext
8
+ import com.facebook.react.uimanager.ViewGroupManager
9
+ import com.facebook.react.uimanager.ViewManagerDelegate
10
+ import com.facebook.react.uimanager.annotations.ReactProp
11
+ import com.facebook.react.viewmanagers.MLRNImagesManagerDelegate
12
+ import com.facebook.react.viewmanagers.MLRNImagesManagerInterface
13
+ import org.maplibre.reactnative.utils.ImageEntry
14
+
15
+ @ReactModule(name = MLRNImagesManager.REACT_CLASS)
16
+ class MLRNImagesManager(
17
+ private val context: ReactApplicationContext,
18
+ ) : ViewGroupManager<MLRNImages>(),
19
+ MLRNImagesManagerInterface<MLRNImages> {
20
+ companion object {
21
+ const val REACT_CLASS = "MLRNImages"
22
+ }
23
+
24
+ private val delegate: MLRNImagesManagerDelegate<MLRNImages, MLRNImagesManager> =
25
+ MLRNImagesManagerDelegate(this)
26
+
27
+ override fun getDelegate(): ViewManagerDelegate<MLRNImages> = delegate
28
+
29
+ override fun getName(): String = REACT_CLASS
30
+
31
+ override fun createViewInstance(context: ThemedReactContext): MLRNImages = MLRNImages(context)
32
+
33
+ @ReactProp(name = "images")
34
+ override fun setImages(
35
+ view: MLRNImages,
36
+ value: Dynamic,
37
+ ) {
38
+ val readableMap = value.asMap()
39
+
40
+ if (readableMap == null) {
41
+ view.setImages(emptyList(), context)
42
+ return
43
+ }
44
+
45
+ val imagesList = mutableListOf<Map.Entry<String, ImageEntry>>()
46
+ val iterator = readableMap.keySetIterator()
47
+
48
+ while (iterator.hasNextKey()) {
49
+ val imageName = iterator.nextKey()
50
+ val imageEntry: ImageEntry =
51
+ when (readableMap.getType(imageName)) {
52
+ ReadableType.Map -> {
53
+ val imageMap = readableMap.getMap(imageName)!!
54
+
55
+ val uri = imageMap.getString("uri")
56
+ val scale =
57
+ if (imageMap.hasKey("scale")) imageMap.getDouble("scale") else null
58
+ val sdf = imageMap.hasKey("sdf") && imageMap.getBoolean("sdf")
59
+
60
+ ImageEntry(uri, scale, sdf)
61
+ }
62
+
63
+ else -> {
64
+ continue
65
+ }
66
+ }
67
+
68
+ imagesList.add(java.util.AbstractMap.SimpleEntry(imageName, imageEntry))
69
+ }
70
+
71
+ view.setImages(imagesList, context)
72
+ }
73
+ }
@@ -57,7 +57,7 @@ public class MLRNStyle {
57
57
  }
58
58
 
59
59
  public ImageEntry imageEntry(MLRNStyleValue styleValue) {
60
- return new ImageEntry(styleValue.getImageURI(), styleValue.getImageScale());
60
+ return new ImageEntry(styleValue.getImageURI(), styleValue.getImageScale(), false);
61
61
  }
62
62
 
63
63
  public void addImage(MLRNStyleValue styleValue, DownloadMapImageTask.OnAllImagesLoaded callback) {
@@ -22,7 +22,7 @@ public class MLRNStyleValue {
22
22
 
23
23
  private String imageURI = "";
24
24
  private boolean isAddImage;
25
- private Double imageScale = ImageEntry.defaultScale;
25
+ private Double imageScale = ImageEntry.DEFAULT_SCALE;
26
26
 
27
27
  public static final int InterpolationModeExponential = 100;
28
28
  public static final int InterpolationModeInterval = 101;
@@ -34,7 +34,7 @@ public class MLRNStyleValue {
34
34
  mPayload = config.getMap("stylevalue");
35
35
 
36
36
  if ("image".equals(mType)) {
37
- imageScale = ImageEntry.defaultScale;
37
+ imageScale = ImageEntry.DEFAULT_SCALE;
38
38
  if ("hashmap".equals(mPayload.getString("type"))) {
39
39
  ReadableMap map = getMap();
40
40
  imageURI = map.getMap("uri").getString("value");
@@ -710,8 +710,9 @@ open class MLRNMapView(
710
710
  return
711
711
  }
712
712
  }
713
+
713
714
  for (images in images) {
714
- images.sendImageMissingEvent(id, this.mapLibreMap!!)
715
+ images.sendImageMissingEvent(id)
715
716
  }
716
717
  }
717
718
 
@@ -15,9 +15,6 @@ public class EventKeys {
15
15
  public static final String POINT_ANNOTATION_DRAG = ns("pointannotation.drag");
16
16
  public static final String POINT_ANNOTATION_DRAG_END = ns("pointannotation.dragend");
17
17
 
18
- // images event
19
- public static final String IMAGES_MISSING = ns("images.missing");
20
-
21
18
 
22
19
  private static String ns(String name) {
23
20
  return String.format("%s.%s", NAMESPACE, name);
@@ -20,6 +20,7 @@ import com.facebook.imagepipeline.image.CloseableStaticBitmap;
20
20
  import com.facebook.imagepipeline.request.ImageRequest;
21
21
  import com.facebook.imagepipeline.request.ImageRequestBuilder;
22
22
  import com.facebook.react.views.imagehelper.ImageSource;
23
+
23
24
  import org.maplibre.android.maps.MapLibreMap;
24
25
  import org.maplibre.android.maps.Style;
25
26
 
@@ -27,7 +28,6 @@ import java.io.File;
27
28
  import java.lang.ref.WeakReference;
28
29
  import java.util.AbstractMap;
29
30
  import java.util.ArrayList;
30
- import java.util.HashMap;
31
31
  import java.util.List;
32
32
  import java.util.Map;
33
33
 
@@ -94,7 +94,7 @@ public class DownloadMapImageTask extends AsyncTask<Map.Entry<String, ImageEntry
94
94
  // Copy the bitmap to make sure it doesn't get recycled when we release
95
95
  // the fresco reference.
96
96
  .copy(Bitmap.Config.ARGB_8888, true);
97
- bitmap.setDensity((int) ((double) DisplayMetrics.DENSITY_DEFAULT * imageEntry.getScaleOr(1.0)));
97
+ bitmap.setDensity((int) ((double) DisplayMetrics.DENSITY_DEFAULT * imageEntry.scale));
98
98
  images.add(new AbstractMap.SimpleEntry<>(object.getKey(), new DownloadedImage(object.getKey(), bitmap, imageEntry)));
99
99
  } else {
100
100
  FLog.e(LOG_TAG, "Failed to load bitmap from: " + uri);
@@ -111,7 +111,7 @@ public class DownloadMapImageTask extends AsyncTask<Map.Entry<String, ImageEntry
111
111
  }
112
112
  }
113
113
  } else {
114
- // local asset required from JS require('image.png') or import icon from 'image.png' while in release mode
114
+ // Local asset required from JS require('image.png') or import icon from 'image.png' while in release mode
115
115
  Bitmap bitmap = BitmapUtils.getBitmapFromResource(context, uri, getBitmapOptions(metrics, imageEntry.scale));
116
116
  if (bitmap != null) {
117
117
  images.add(new AbstractMap.SimpleEntry<>(object.getKey(), new DownloadedImage(object.getKey(), bitmap, imageEntry)));
@@ -145,9 +145,11 @@ public class DownloadMapImageTask extends AsyncTask<Map.Entry<String, ImageEntry
145
145
  BitmapFactory.Options options = new BitmapFactory.Options();
146
146
  options.inScreenDensity = metrics.densityDpi;
147
147
  options.inTargetDensity = metrics.densityDpi;
148
- if (scale != ImageEntry.defaultScale) {
148
+
149
+ if (scale != ImageEntry.DEFAULT_SCALE) {
149
150
  options.inDensity = (int) ((double) DisplayMetrics.DENSITY_DEFAULT * scale);
150
151
  }
152
+
151
153
  return options;
152
154
  }
153
155
  }
@@ -0,0 +1,14 @@
1
+ package org.maplibre.reactnative.utils
2
+
3
+ class ImageEntry(
4
+ @JvmField var uri: String?,
5
+ scale: Double?,
6
+ @JvmField var sdf: Boolean,
7
+ ) {
8
+ @JvmField
9
+ var scale: Double = scale ?: DEFAULT_SCALE
10
+
11
+ companion object {
12
+ const val DEFAULT_SCALE: Double = 1.0
13
+ }
14
+ }
@@ -100,23 +100,28 @@ typedef NS_ENUM(NSInteger, MLRNImageQueueOperationState) {
100
100
  }
101
101
  __weak MLRNImageQueueOperation *weakSelf = self;
102
102
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
103
- [weakSelf setCancellationBlock:
104
- [[weakSelf.bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES]
105
- loadImageWithURLRequest:weakSelf.urlRequest
106
- size:CGSizeZero
107
- scale:weakSelf.scale
108
- clipped:YES
109
- resizeMode:RCTResizeModeStretch
110
- progressBlock:nil
111
- partialLoadBlock:nil
112
- completionBlock:^void(NSError *error, UIImage *image) {
113
- if (image && weakSelf.sdf) {
114
- image = [image
115
- imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
116
- }
117
- weakSelf.completionHandler(error, image);
118
- [weakSelf setState:IOState_Finished except:IOState_Finished];
119
- }]];
103
+ [weakSelf
104
+ setCancellationBlock:[[weakSelf.bridge moduleForName:@"ImageLoader"
105
+ lazilyLoadIfNecessary:YES]
106
+ loadImageWithURLRequest:weakSelf.urlRequest
107
+ size:CGSizeZero
108
+ scale:weakSelf.scale
109
+ clipped:YES
110
+ resizeMode:RCTResizeModeStretch
111
+ progressBlock:^(int64_t progress, int64_t total) {
112
+ // No-op
113
+ }
114
+ partialLoadBlock:^(UIImage *image) {
115
+ // No-op
116
+ }
117
+ completionBlock:^void(NSError *error, UIImage *image) {
118
+ if (image && weakSelf.sdf) {
119
+ image = [image
120
+ imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
121
+ }
122
+ weakSelf.completionHandler(error, image);
123
+ [weakSelf setState:IOState_Finished except:IOState_Finished];
124
+ }]];
120
125
  if ([weakSelf setState:IOState_Executing
121
126
  only:IOState_Initial] == IOState_CancelledDoNotExecute) {
122
127
  [weakSelf callCancellationBlock];
@@ -10,15 +10,20 @@
10
10
 
11
11
  @property (nonatomic, weak) RCTBridge *bridge;
12
12
 
13
- @property (nonatomic, strong) MLRNMapView *map;
13
+ @property (nonatomic, strong) MLRNMapView * _Nullable map;
14
+ @property (nonatomic, strong, nonnull) NSMutableArray<id<RCTComponent>> *reactSubviews;
14
15
 
15
- @property (nonatomic, strong) NSDictionary<NSString *, NSString *> *images;
16
- @property (nonatomic, strong) NSArray<NSString *> *nativeImages;
16
+ @property (nonatomic, strong, nonnull) NSDictionary * images;
17
17
 
18
- @property (nonatomic, copy) RCTBubblingEventBlock onImageMissing;
19
- @property (nonatomic, assign) BOOL hasOnImageMissing;
18
+ @property (nonatomic, copy, nullable) RCTDirectEventBlock onImageMissing;
20
19
 
21
- - (BOOL)addMissingImageToStyle:(NSString *)imageName;
22
- - (void)sendImageMissingEvent:(NSString *)imageName;
20
+ - (void)addToMap;
21
+ - (void)removeFromMap;
22
+
23
+ - (BOOL)addMissingImageToStyle:(NSString *_Nonnull)imageName;
24
+ - (void)sendImageMissingEvent:(NSString *_Nonnull)imageName;
25
+
26
+ - (void)insertReactSubview:(id<RCTComponent>_Nullable)subview atIndex:(NSInteger)atIndex;
27
+ - (void)removeReactSubview:(id<RCTComponent>_Nullable)subview;
23
28
 
24
29
  @end
@@ -9,76 +9,149 @@
9
9
 
10
10
  static UIImage *_placeHolderImage;
11
11
 
12
+ - (instancetype)initWithFrame:(CGRect)frame {
13
+ if (self = [super initWithFrame:frame]) {
14
+ _reactSubviews = [[NSMutableArray alloc] init];
15
+ }
16
+ return self;
17
+ }
18
+
19
+ #pragma clang diagnostic push
20
+ #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
21
+ - (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex {
22
+ [_reactSubviews insertObject:subview atIndex:atIndex];
23
+ }
24
+ #pragma clang diagnostic pop
25
+
26
+ #pragma clang diagnostic push
27
+ #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
28
+ - (void)removeReactSubview:(id<RCTComponent>)subview {
29
+ [_reactSubviews removeObject:subview];
30
+ }
31
+ #pragma clang diagnostic pop
32
+
33
+ #pragma clang diagnostic push
34
+ #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
35
+ - (NSArray<id<RCTComponent>> *)reactSubviews {
36
+ return _reactSubviews;
37
+ }
38
+ #pragma clang diagnostic pop
39
+
12
40
  - (void)addToMap {
13
41
  if (self.map.style == nil) {
14
42
  return;
15
43
  }
16
- [self _addNativeImages:_nativeImages];
17
- [self _addRemoteImages:_images];
44
+ [self _processImages:_images];
18
45
  }
19
46
 
20
47
  - (void)removeFromMap {
21
48
  if (self.map.style == nil) {
22
49
  return;
23
50
  }
24
-
25
51
  [self _removeImages];
26
52
  }
27
53
 
28
54
  - (void)_removeImages {
29
- if ([self _hasImages]) {
30
- NSArray<NSString *> *imageNames = _images.allKeys;
55
+ if (_images == nil || _images.count == 0) {
56
+ return;
57
+ }
31
58
 
32
- for (NSString *imageName in imageNames) {
33
- [self.map.style removeImageForName:imageName];
34
- }
59
+ for (NSString *imageName in _images.allKeys) {
60
+ [self.map.style removeImageForName:imageName];
35
61
  }
62
+ }
63
+
64
+ - (BOOL)_isNativeImageString:(NSString *)value {
65
+ return [UIImage imageNamed:value] != nil;
66
+ }
67
+
68
+ - (void)_processImages:(NSDictionary *)images {
69
+ if (!images || images.count == 0) return;
36
70
 
37
- if ([self _hasNativeImages]) {
38
- for (NSString *imageName in _nativeImages) {
39
- [self.map.style removeImageForName:imageName];
71
+ NSMutableDictionary *nativeAssets = [NSMutableDictionary new];
72
+ NSMutableDictionary *remoteImages = [NSMutableDictionary new];
73
+
74
+ for (NSString *imageName in images.allKeys) {
75
+ id value = images[imageName];
76
+
77
+ if ([value isKindOfClass:[NSString class]]) {
78
+ NSString *stringValue = (NSString *)value;
79
+ if ([self _isNativeImageString:stringValue]) {
80
+ nativeAssets[imageName] = stringValue;
81
+ } else {
82
+ remoteImages[imageName] = @{@"uri" : stringValue};
83
+ }
84
+ } else if ([value isKindOfClass:[NSDictionary class]]) {
85
+ remoteImages[imageName] = value;
40
86
  }
41
87
  }
88
+
89
+ // Add native assets first (synchronous)
90
+ if (nativeAssets.count > 0) {
91
+ [self _addNativeImages:nativeAssets];
92
+ }
93
+
94
+ // Then add remote images (may be async)
95
+ if (remoteImages.count > 0) {
96
+ [self _addRemoteImages:remoteImages];
97
+ }
42
98
  }
43
99
 
44
100
  - (BOOL)addMissingImageToStyle:(NSString *)imageName {
45
- if (_nativeImages && [_nativeImages containsObject:imageName]) {
46
- [self _addNativeImages:@[ imageName ]];
47
- return true;
48
- }
101
+ if (_images == nil) return false;
102
+
103
+ id value = _images[imageName];
104
+ if (value == nil) return false;
49
105
 
50
- NSString *remoteImage = _images != nil ? [_images objectForKey:imageName] : nil;
51
- if (remoteImage) {
52
- [self _addRemoteImages:@{imageName : remoteImage}];
106
+ if ([value isKindOfClass:[NSString class]]) {
107
+ NSString *stringValue = (NSString *)value;
108
+ if ([self _isNativeImageString:stringValue]) {
109
+ [self _addNativeImages:@{imageName : stringValue}];
110
+ } else {
111
+ [self _addRemoteImages:@{imageName : @{@"uri" : stringValue}}];
112
+ }
113
+ return true;
114
+ } else if ([value isKindOfClass:[NSDictionary class]]) {
115
+ [self _addRemoteImages:@{imageName : value}];
53
116
  return true;
54
117
  }
118
+
55
119
  return false;
56
120
  }
57
121
 
58
122
  - (void)sendImageMissingEvent:(NSString *)imageName {
59
- NSDictionary *payload = @{@"imageKey" : imageName};
123
+ NSDictionary *payload = @{@"image" : imageName};
60
124
  MLRNEvent *event = [MLRNEvent makeEvent:RCT_MLRN_MISSING_IMAGE withPayload:payload];
61
125
  if (_onImageMissing) {
62
126
  _onImageMissing([event toJSON]);
63
127
  }
64
128
  }
65
129
 
66
- - (void)_addNativeImages:(NSArray<NSString *> *)nativeImages {
67
- if (!nativeImages) return;
130
+ - (void)_addNativeImages:(NSDictionary *)nativeImages {
131
+ if (!nativeImages || nativeImages.count == 0) return;
68
132
 
69
- for (NSString *imageName in nativeImages) {
70
- // only add native images if they are not in the style yet (similar to [MLRNUtils fetchImages:
71
- // style:])
133
+ for (NSString *imageName in nativeImages.allKeys) {
72
134
  if (![self.map.style imageForName:imageName]) {
73
- UIImage *image = [UIImage imageNamed:imageName];
74
- [self.map.style setImage:image forName:imageName];
135
+ // Get the asset name from the dictionary value
136
+ NSString *assetName = nativeImages[imageName];
137
+ if ([assetName isKindOfClass:[NSString class]]) {
138
+ UIImage *image = [UIImage imageNamed:assetName];
139
+ if (image) {
140
+ [self.map.style setImage:image forName:imageName];
141
+ }
142
+ }
75
143
  }
76
144
  }
77
145
  }
78
146
 
79
- - (void)_addRemoteImages:(NSDictionary<NSString *, NSString *> *)remoteImages {
80
- if (!remoteImages) return;
81
- NSDictionary<NSString *, NSString *> *missingImages = [NSMutableDictionary new];
147
+ /**
148
+ * Add remote images with async loading.
149
+ * remoteImages is a dictionary where values are { uri: string, scale?: number, sdf?: boolean }
150
+ */
151
+ - (void)_addRemoteImages:(NSDictionary *)remoteImages {
152
+ if (!remoteImages || remoteImages.count == 0) return;
153
+
154
+ NSMutableDictionary *missingImages = [NSMutableDictionary new];
82
155
 
83
156
  // Add image placeholder for images that are not yet available in the style. This way
84
157
  // we can load the images asynchronously and add the GeoJSONSource to the map without delay.
@@ -90,7 +163,7 @@ static UIImage *_placeHolderImage;
90
163
  for (NSString *imageName in remoteImages.allKeys) {
91
164
  if (![self.map.style imageForName:imageName]) {
92
165
  [self.map.style setImage:[MLRNImages placeholderImage] forName:imageName];
93
- [missingImages setValue:_images[imageName] forKey:imageName];
166
+ missingImages[imageName] = remoteImages[imageName];
94
167
  }
95
168
  }
96
169
 
@@ -98,21 +171,13 @@ static UIImage *_placeHolderImage;
98
171
  // forceUpdate to ensure the placeholder images are updated
99
172
  [MLRNUtils fetchImages:_bridge
100
173
  style:self.map.style
101
- objects:_images
174
+ objects:missingImages
102
175
  forceUpdate:true
103
176
  callback:^{
104
177
  }];
105
178
  }
106
179
  }
107
180
 
108
- - (BOOL)_hasImages {
109
- return _images != nil && _images.count > 0;
110
- }
111
-
112
- - (BOOL)_hasNativeImages {
113
- return _nativeImages != nil && _nativeImages.count > 0;
114
- }
115
-
116
181
  + (UIImage *)placeholderImage {
117
182
  if (_placeHolderImage) return _placeHolderImage;
118
183
  UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0.0);
@@ -0,0 +1,9 @@
1
+ #import <React/RCTViewComponentView.h>
2
+
3
+ @class MLRNImages;
4
+
5
+ @interface MLRNImagesComponentView : RCTViewComponentView
6
+
7
+ @property (nonatomic, readonly) MLRNImages *images;
8
+
9
+ @end