diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/RCTMGLPackage.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/RCTMGLPackage.java index af872e7..255bb52 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/RCTMGLPackage.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/RCTMGLPackage.java @@ -54,7 +54,7 @@ public class RCTMGLPackage implements ReactPackage { // sources managers.add(new RCTMGLVectorSourceManager(reactApplicationContext)); - managers.add(new RCTMGLShapeSourceManager()); + managers.add(new RCTMGLShapeSourceManager(reactApplicationContext)); managers.add(new RCTMGLRasterSourceManager()); // layers diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/layers/RCTMGLSymbolLayer.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/layers/RCTMGLSymbolLayer.java index bb50a7b..f0df579 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/layers/RCTMGLSymbolLayer.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/layers/RCTMGLSymbolLayer.java @@ -4,6 +4,7 @@ import android.content.Context; import com.mapbox.mapboxsdk.style.layers.Filter; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; +import com.mapbox.rctmgl.R; import com.mapbox.rctmgl.components.mapview.RCTMGLMapView; import com.mapbox.rctmgl.components.styles.RCTMGLStyle; import com.mapbox.rctmgl.components.styles.RCTMGLStyleFactory; diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java index cb405c7..7aae3ac 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java @@ -1,6 +1,8 @@ package com.mapbox.rctmgl.components.styles.sources; import android.content.Context; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.style.sources.GeoJsonOptions; @@ -34,6 +36,7 @@ public class RCTMGLShapeSource extends RCTSource { private Double mTolerance; private List> mImages; + private List> mNativeImages; public RCTMGLShapeSource(Context context) { super(context); @@ -41,9 +44,22 @@ public class RCTMGLShapeSource extends RCTSource { @Override public void addToMap(final RCTMGLMapView mapView) { - if (hasImages()) { - MapboxMap map = mapView.getMapboxMap(); + if (!hasNativeImages() && !hasImages()) { + super.addToMap(mapView); + return; + } + MapboxMap map = mapView.getMapboxMap(); + + // add all images from drawables folder + if (hasNativeImages()) { + for (Map.Entry nativeImage : mNativeImages) { + map.addImage(nativeImage.getKey(), nativeImage.getValue().getBitmap()); + } + } + + // add all external images from javascript layer + if (hasImages()) { DownloadMapImageTask.OnAllImagesLoaded imagesLoadedCallback = new DownloadMapImageTask.OnAllImagesLoaded() { @Override public void onAllImagesLoaded() { @@ -53,9 +69,10 @@ public class RCTMGLShapeSource extends RCTSource { DownloadMapImageTask task = new DownloadMapImageTask(map, imagesLoadedCallback); task.execute(mImages.toArray(new Map.Entry[mImages.size()])); - } else { - super.addToMap(mapView); + return; } + + super.addToMap(mapView); } @Override @@ -67,6 +84,12 @@ public class RCTMGLShapeSource extends RCTSource { mMap.removeImage(image.getKey()); } } + + if (hasNativeImages()) { + for (Map.Entry image : mNativeImages) { + mMap.removeImage(image.getKey()); + } + } } @Override @@ -124,6 +147,10 @@ public class RCTMGLShapeSource extends RCTSource { mImages = images; } + public void setNativeImages(List> nativeImages) { + mNativeImages = nativeImages; + } + private GeoJsonOptions getOptions() { GeoJsonOptions options = new GeoJsonOptions(); @@ -157,4 +184,8 @@ public class RCTMGLShapeSource extends RCTSource { private boolean hasImages() { return mImages != null && mImages.size() > 0; } + + private boolean hasNativeImages() { + return mNativeImages != null && mNativeImages.size() > 0; + } } diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java index 294327c..2bad13a 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java @@ -1,12 +1,19 @@ package com.mapbox.rctmgl.components.styles.sources; +import android.content.Context; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.util.Log; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; +import com.mapbox.rctmgl.components.mapview.RCTMGLMapView; +import com.mapbox.rctmgl.utils.ResourceUtils; import java.net.MalformedURLException; import java.net.URL; @@ -23,6 +30,12 @@ public class RCTMGLShapeSourceManager extends ViewGroupManager> resources = new ArrayList<>(); + + for (int i = 0; i < arr.size(); i++) { + String resourceName = arr.getString(i); + BitmapDrawable drawable = (BitmapDrawable) ResourceUtils.getDrawableByName(mContext, resourceName); + + if (drawable != null) { + resources.add(new AbstractMap.SimpleEntry(resourceName, drawable)); + } + } + + source.setNativeImages(resources); + } } diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/utils/ResourceUtils.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/utils/ResourceUtils.java new file mode 100644 index 0000000..7fa10e3 --- /dev/null +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/utils/ResourceUtils.java @@ -0,0 +1,30 @@ +package com.mapbox.rctmgl.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; + +/** + * Created by nickitaliano on 10/19/17. + */ + +public class ResourceUtils { + public static Drawable getDrawableByName(Context context, String resourceName) { + if (context == null || resourceName == null || resourceName.isEmpty()) { + return null; + } + + Resources resources = context.getResources(); + if (resources == null) { + return null; + } + + final int resID = resources.getIdentifier(resourceName, "drawable", context.getPackageName()); + if (resID == 0) { + return null; + } + + return ContextCompat.getDrawable(context, resID); + } +} diff --git a/ios/RCTMGL/RCTMGLShapeSource.h b/ios/RCTMGL/RCTMGLShapeSource.h index 6fc8f44..70c3fae 100644 --- a/ios/RCTMGL/RCTMGLShapeSource.h +++ b/ios/RCTMGL/RCTMGLShapeSource.h @@ -18,6 +18,7 @@ @property (nonatomic, copy) NSString *url; @property (nonatomic, copy) NSString *shape; @property (nonatomic, strong) NSDictionary *images; +@property (nonatomic, strong) NSArray *nativeImages; @property (nonatomic, assign) NSNumber *cluster; @property (nonatomic, assign) NSNumber *clusterRadius; diff --git a/ios/RCTMGL/RCTMGLShapeSource.m b/ios/RCTMGL/RCTMGLShapeSource.m index a2db600..b74a7c3 100644 --- a/ios/RCTMGL/RCTMGLShapeSource.m +++ b/ios/RCTMGL/RCTMGLShapeSource.m @@ -27,9 +27,15 @@ return; } - if (_images == nil || _images.count == 0) { + if (![self _hasImages] && ![self _hasNativeImages]) { [super addToMap]; } else { + if ([self _hasNativeImages]) { + for (NSString *imageName in _nativeImages) { + UIImage *image = [UIImage imageNamed:imageName]; + [self.map.style setImage:image forName:imageName]; + } + } [RCTMGLUtils fetchImages:_bridge style:self.map.style objects:_images callback:^{ [super addToMap]; }]; } } @@ -42,13 +48,19 @@ [super removeFromMap]; - if (_images != nil && _images.count > 0) { + if ([self _hasImages]) { NSArray *imageNames = _images.allKeys; for (NSString *imageName in imageNames) { [self.map.style removeImageForName:imageName]; } } + + if ([self _hasNativeImages]) { + for (NSString *imageName in _nativeImages) { + [self.map.style removeImageForName:imageName]; + } + } } - (MGLSource*)makeSource @@ -95,4 +107,14 @@ return options; } +- (BOOL)_hasImages +{ + return _images != nil && _images.count > 0; +} + +- (BOOL)_hasNativeImages +{ + return _nativeImages != nil && _nativeImages.count > 0; +} + @end diff --git a/ios/RCTMGL/RCTMGLShapeSourceManager.m b/ios/RCTMGL/RCTMGLShapeSourceManager.m index be6dbfb..9e5de99 100644 --- a/ios/RCTMGL/RCTMGLShapeSourceManager.m +++ b/ios/RCTMGL/RCTMGLShapeSourceManager.m @@ -24,6 +24,7 @@ RCT_EXPORT_VIEW_PROPERTY(maxZoom, NSNumber) RCT_EXPORT_VIEW_PROPERTY(buffer, NSNumber) RCT_EXPORT_VIEW_PROPERTY(tolerance, NSNumber) RCT_EXPORT_VIEW_PROPERTY(images, NSDictionary) +RCT_EXPORT_VIEW_PROPERTY(nativeImages, NSArray) - (UIView*)view { diff --git a/ios/RCTMGL/RCTMGLUtils.m b/ios/RCTMGL/RCTMGLUtils.m index 58bdf94..73f73c8 100644 --- a/ios/RCTMGL/RCTMGLUtils.m +++ b/ios/RCTMGL/RCTMGLUtils.m @@ -65,42 +65,43 @@ static double const MS_TO_S = 0.001; + (void)fetchImages:(RCTBridge *)bridge style:(MGLStyle *)style objects:(NSDictionary*)objects callback:(void (^)())callback { - dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_queue_t mainQueue = dispatch_get_main_queue(); - - dispatch_group_t imageQueueGroup = dispatch_group_create(); + if (objects == nil) { + callback(); + return; + } NSArray *imageNames = objects.allKeys; + if (imageNames.count == 0) { + callback(); + return; + } + + dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block NSUInteger imagesLeftToLoad = imageNames.count; - dispatch_group_async(imageQueueGroup, concurrentQueue, ^{ - dispatch_group_enter(imageQueueGroup); + void (^imageLoadedBlock)() = ^{ + imagesLeftToLoad--; - void (^imageLoadedBlock)() = ^{ - imagesLeftToLoad--; - - if (imagesLeftToLoad == 0) { - dispatch_group_leave(imageQueueGroup); - } - }; - - for (NSString *imageName in imageNames) { - UIImage *foundImage = [style imageForName:imageName]; - - if (foundImage == nil) { - [RCTMGLUtils fetchImage:bridge url:objects[imageName] callback:^(NSError *error, UIImage *image) { - dispatch_async(mainQueue, ^{ - [style setImage:image forName:imageName]; - imageLoadedBlock(); - }); - }]; - } else { - imageLoadedBlock(); - } + if (imagesLeftToLoad == 0) { + dispatch_semaphore_signal(sema); } - }); + }; - dispatch_group_notify(imageQueueGroup, mainQueue, ^{ callback(); }); + for (NSString *imageName in imageNames) { + UIImage *foundImage = [style imageForName:imageName]; + + if (foundImage == nil) { + [RCTMGLUtils fetchImage:bridge url:objects[imageName] callback:^(NSError *error, UIImage *image) { + [style setImage:image forName:imageName]; + imageLoadedBlock(); + }]; + } else { + imageLoadedBlock(); + } + } + + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + callback(); } @end diff --git a/javascript/components/ShapeSource.js b/javascript/components/ShapeSource.js index d950782..93729df 100644 --- a/javascript/components/ShapeSource.js +++ b/javascript/components/ShapeSource.js @@ -8,13 +8,17 @@ const MapboxGL = NativeModules.MGLModule; export const NATIVE_MODULE_NAME = 'RCTMGLShapeSource'; -const RCTMGLShapeSource = requireNativeComponent(NATIVE_MODULE_NAME, ShapeSource); +const RCTMGLShapeSource = requireNativeComponent(NATIVE_MODULE_NAME, ShapeSource, { + nativeOnly: { nativeImages: true }, +}); /** * ShapeSource is a map content source that supplies vector shapes to be shown on the map. * The shape may be a url or a GeoJSON object */ class ShapeSource extends React.Component { + static NATIVE_ASSETS_KEY = 'assets'; + static propTypes = { /** * A string that uniquely identifies the source. @@ -73,8 +77,10 @@ class ShapeSource extends React.Component { tolerance: PropTypes.number, /** -- * Specifies the external images in key-value pairs required for the shape source. -- */ + * Specifies the external images in key-value pairs required for the shape source. + * If you have an asset under Image.xcassets on iOS and the drawables directory on android + * you can specify an array of string names with assets as the key `{ assets: ['pin'] }`. + */ images: PropTypes.object, }; @@ -96,17 +102,25 @@ class ShapeSource extends React.Component { } let images = {}; + let nativeImages = []; const imageNames = Object.keys(this.props.images); for (let imageName of imageNames) { - const res = resolveAssetSource(this.props.images[imageName]); + if (imageName === ShapeSource.NATIVE_ASSETS_KEY && Array.isArray(this.props.images[ShapeSource.NATIVE_ASSETS_KEY])) { + nativeImages = this.props.images[ShapeSource.NATIVE_ASSETS_KEY]; + continue; + } + const res = resolveAssetSource(this.props.images[imageName]); if (res && res.uri) { images[imageName] = res.uri; } } - return images; + return { + images: images, + nativeImages: nativeImages, + }; } render () { @@ -120,7 +134,7 @@ class ShapeSource extends React.Component { maxZoomLevel: this.props.maxZoomLevel, buffer: this.props.buffer, tolerance: this.props.tolerance, - images: this._getImages(), + ...this._getImages(), }; return (