From 17835e01a522ccad6fcc79bcb4328e70510fbbf2 Mon Sep 17 00:00:00 2001 From: Pavlo Aksonov Date: Thu, 22 Sep 2016 18:03:05 +0200 Subject: [PATCH 1/5] support for custom annotation views --- MapboxAnnotation.js | 283 +++++++++++++++++++ index.js | 2 + ios/RCTMapboxGL.xcodeproj/project.pbxproj | 12 + ios/RCTMapboxGL/RCTMapboxAnnotation.h | 50 ++++ ios/RCTMapboxGL/RCTMapboxAnnotation.m | 24 ++ ios/RCTMapboxGL/RCTMapboxAnnotationManager.h | 14 + ios/RCTMapboxGL/RCTMapboxAnnotationManager.m | 41 +++ ios/RCTMapboxGL/RCTMapboxGL.m | 45 +++ 8 files changed, 471 insertions(+) create mode 100644 MapboxAnnotation.js create mode 100644 ios/RCTMapboxGL/RCTMapboxAnnotation.h create mode 100644 ios/RCTMapboxGL/RCTMapboxAnnotation.m create mode 100644 ios/RCTMapboxGL/RCTMapboxAnnotationManager.h create mode 100644 ios/RCTMapboxGL/RCTMapboxAnnotationManager.m diff --git a/MapboxAnnotation.js b/MapboxAnnotation.js new file mode 100644 index 000000000..2b124d4c0 --- /dev/null +++ b/MapboxAnnotation.js @@ -0,0 +1,283 @@ +import React, { PropTypes } from 'react'; +import { + View, + requireNativeComponent, + StyleSheet, + Platform, + NativeModules, + Animated, + findNodeHandle, +} from 'react-native'; + +import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; + +const viewConfig = { + uiViewClassName: 'RCTMapboxAnnotation', + validAttributes: { + coordinate: true, + }, +}; + +const propTypes = { + ...View.propTypes, + + // TODO(lmr): get rid of these? + identifier: PropTypes.string, + reuseIdentifier: PropTypes.string, + + /** + * The title of the marker. This is only used if the component has no children that + * are an ``, in which case the default callout behavior will be used, which + * will show both the `title` and the `description`, if provided. + */ + title: PropTypes.string, + + /** + * The title of the marker. This is only used if the component has no children that + * are an ``, in which case the default callout behavior will be used, which + * will show both the `title` and the `description`, if provided. + */ + subtitle: PropTypes.string, + + /** + * The description of the marker. This is only used if the component has no children + * that are an ``, in which case the default callout behavior will be used, + * which will show both the `title` and the `description`, if provided. + */ + description: PropTypes.string, + + /** + * A custom image to be used as the marker's icon. Only local image resources are allowed to be + * used. + */ + image: PropTypes.any, + + /** + * If no custom marker view or custom image is provided, the platform default pin will be used, + * which can be customized by this color. Ignored if a custom marker is being used. + */ + pinColor: PropTypes.string, + + /** + * The coordinate for the marker. + */ + coordinate: PropTypes.shape({ + /** + * Coordinates for the anchor point of the marker. + */ + latitude: PropTypes.number.isRequired, + longitude: PropTypes.number.isRequired, + }).isRequired, + + /** + * The offset (in points) at which to display the view. + * + * By default, the center point of an annotation view is placed at the coordinate point of the + * associated annotation. You can use this property to reposition the annotation view as + * needed. This x and y offset values are measured in points. Positive offset values move the + * annotation view down and to the right, while negative values move it up and to the left. + * + * For android, see the `anchor` prop. + * + * @platform ios + */ + centerOffset: PropTypes.shape({ + /** + * Offset from the anchor point + */ + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + + /** + * The offset (in points) at which to place the callout bubble. + * + * This property determines the additional distance by which to move the callout bubble. When + * this property is set to (0, 0), the anchor point of the callout bubble is placed on the + * top-center point of the marker view’s frame. Specifying positive offset values moves the + * callout bubble down and to the right, while specifying negative values moves it up and to + * the left. + * + * For android, see the `calloutAnchor` prop. + * + * @platform ios + */ + calloutOffset: PropTypes.shape({ + /** + * Offset to the callout + */ + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + + /** + * Sets the anchor point for the marker. + * + * The anchor specifies the point in the icon image that is anchored to the marker's position + * on the Earth's surface. + * + * The anchor point is specified in the continuous space [0.0, 1.0] x [0.0, 1.0], where (0, 0) + * is the top-left corner of the image, and (1, 1) is the bottom-right corner. The anchoring + * point in a W x H image is the nearest discrete grid point in a (W + 1) x (H + 1) grid, + * obtained by scaling the then rounding. For example, in a 4 x 2 image, the anchor point + * (0.7, 0.6) resolves to the grid point at (3, 1). + * + * For ios, see the `centerOffset` prop. + * + * @platform android + */ + anchor: PropTypes.shape({ + /** + * Offset to the callout + */ + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + + /** + * Specifies the point in the marker image at which to anchor the callout when it is displayed. + * This is specified in the same coordinate system as the anchor. See the `andor` prop for more + * details. + * + * The default is the top middle of the image. + * + * For ios, see the `calloutOffset` prop. + * + * @platform android + */ + calloutAnchor: PropTypes.shape({ + /** + * Offset to the callout + */ + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + + /** + * Sets whether this marker should be flat against the map true or a billboard facing the + * camera false. + * + * @platform android + */ + flat: PropTypes.bool, + + draggable: PropTypes.bool, + + /** + * Callback that is called when the user presses on the marker + */ + onPress: PropTypes.func, + + /** + * Callback that is called when the user selects the marker, before the callout is shown. + * + * @platform ios + */ + onSelect: PropTypes.func, + + /** + * Callback that is called when the marker is deselected, before the callout is hidden. + * + * @platform ios + */ + onDeselect: PropTypes.func, + + /** + * Callback that is called when the user taps the callout view. + */ + onCalloutPress: PropTypes.func, + + /** + * Callback that is called when the user initiates a drag on this marker (if it is draggable) + */ + onDragStart: PropTypes.func, + + /** + * Callback called continuously as the marker is dragged + */ + onDrag: PropTypes.func, + + /** + * Callback that is called when a drag on this marker finishes. This is usually the point you + * will want to setState on the marker's coordinate again + */ + onDragEnd: PropTypes.func, +}; + +const defaultProps = { + onPress() {}, +}; + +class MapboxAnnotation extends React.Component { + constructor(props) { + super(props); + + this.showCallout = this.showCallout.bind(this); + this.hideCallout = this.hideCallout.bind(this); + } + + showCallout() { + this._runCommand('showCallout', []); + } + + hideCallout() { + this._runCommand('hideCallout', []); + } + + _getHandle() { + return findNodeHandle(this.marker); + } + + _runCommand(name, args) { + switch (Platform.OS) { + case 'android': + NativeModules.UIManager.dispatchViewManagerCommand( + this._getHandle(), + NativeModules.UIManager.RCTMapboxAnnotation.Commands[name], + args + ); + break; + + case 'ios': + NativeModules.RCTMapboxAnnotationManager[name].apply( + NativeModules.RCTMapboxAnnotationManager[name], + [this._getHandle(), ...args] + ); + break; + + default: + break; + } + } + + render() { + let image; + if (this.props.image) { + image = resolveAssetSource(this.props.image) || {}; + image = image.uri; + } + + return ( + { this.marker = ref; }} + {...this.props} + image={image} + style={[styles.marker, this.props.style]} + /> + ); + } +} + +MapboxAnnotation.propTypes = propTypes; +MapboxAnnotation.defaultProps = defaultProps; +MapboxAnnotation.viewConfig = viewConfig; + +const styles = StyleSheet.create({ + marker: { + position: 'absolute', + backgroundColor: 'transparent', + }, +}); + +const RCTMapboxAnnotation = requireNativeComponent('RCTMapboxAnnotation', MapboxAnnotation); +module.exports = MapboxAnnotation; diff --git a/index.js b/index.js index 0c9a445bd..a0811a98e 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ import { import cloneDeep from 'lodash/cloneDeep'; import clone from 'lodash/clone'; import isEqual from 'lodash/isEqual'; +import Annotation from './MapboxAnnotation'; const { MapboxGLManager } = NativeModules; const { mapStyles, userTrackingMode, userLocationVerticalAlignment, unknownResourceCount } = MapboxGLManager; @@ -438,6 +439,7 @@ const MapboxGLView = requireNativeComponent('RCTMapboxGL', MapView, { const Mapbox = { MapView, + Annotation, mapStyles, userTrackingMode, userLocationVerticalAlignment, unknownResourceCount, getMetricsEnabled, setMetricsEnabled, setAccessToken, diff --git a/ios/RCTMapboxGL.xcodeproj/project.pbxproj b/ios/RCTMapboxGL.xcodeproj/project.pbxproj index e8b3e7d99..bfac5c826 100644 --- a/ios/RCTMapboxGL.xcodeproj/project.pbxproj +++ b/ios/RCTMapboxGL.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 874C0C441D9436D80034AF3F /* RCTMapboxAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 874C0C401D9436D80034AF3F /* RCTMapboxAnnotation.m */; }; + 874C0C451D9436D80034AF3F /* RCTMapboxAnnotationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 874C0C421D9436D80034AF3F /* RCTMapboxAnnotationManager.m */; }; C167F89C1D18112B007C7A42 /* RCTMapboxGLConversions.m in Sources */ = {isa = PBXBuildFile; fileRef = C167F89B1D18112B007C7A42 /* RCTMapboxGLConversions.m */; }; C5DBB3441AF2EF2B00E611A9 /* RCTMapboxGL.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = C5DBB3431AF2EF2B00E611A9 /* RCTMapboxGL.h */; }; C5DBB3461AF2EF2B00E611A9 /* RCTMapboxGL.m in Sources */ = {isa = PBXBuildFile; fileRef = C5DBB3451AF2EF2B00E611A9 /* RCTMapboxGL.m */; }; @@ -38,6 +40,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 874C0C401D9436D80034AF3F /* RCTMapboxAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapboxAnnotation.m; sourceTree = ""; }; + 874C0C411D9436D80034AF3F /* RCTMapboxAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapboxAnnotation.h; sourceTree = ""; }; + 874C0C421D9436D80034AF3F /* RCTMapboxAnnotationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapboxAnnotationManager.m; sourceTree = ""; }; + 874C0C431D9436D80034AF3F /* RCTMapboxAnnotationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapboxAnnotationManager.h; sourceTree = ""; }; C167F89A1D18111F007C7A42 /* RCTMapboxGLConversions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTMapboxGLConversions.h; sourceTree = ""; }; C167F89B1D18112B007C7A42 /* RCTMapboxGLConversions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapboxGLConversions.m; sourceTree = ""; }; C5DBB3401AF2EF2B00E611A9 /* libRCTMapboxGL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTMapboxGL.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -89,6 +95,10 @@ C5DBB3421AF2EF2B00E611A9 /* RCTMapboxGL */ = { isa = PBXGroup; children = ( + 874C0C401D9436D80034AF3F /* RCTMapboxAnnotation.m */, + 874C0C411D9436D80034AF3F /* RCTMapboxAnnotation.h */, + 874C0C421D9436D80034AF3F /* RCTMapboxAnnotationManager.m */, + 874C0C431D9436D80034AF3F /* RCTMapboxAnnotationManager.h */, C5DBB3431AF2EF2B00E611A9 /* RCTMapboxGL.h */, C5DBB3451AF2EF2B00E611A9 /* RCTMapboxGL.m */, C5DBB3641AF2EFB500E611A9 /* RCTMapboxGLManager.h */, @@ -206,6 +216,8 @@ C5DBB3461AF2EF2B00E611A9 /* RCTMapboxGL.m in Sources */, C5DBB3661AF2EFB500E611A9 /* RCTMapboxGLManager.m in Sources */, C167F89C1D18112B007C7A42 /* RCTMapboxGLConversions.m in Sources */, + 874C0C451D9436D80034AF3F /* RCTMapboxAnnotationManager.m in Sources */, + 874C0C441D9436D80034AF3F /* RCTMapboxAnnotation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotation.h b/ios/RCTMapboxGL/RCTMapboxAnnotation.h new file mode 100644 index 000000000..742cc765a --- /dev/null +++ b/ios/RCTMapboxGL/RCTMapboxAnnotation.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTMapboxAnnotation.h" + +#import +#import + +#import "RCTConvert+MapKit.h" +#import "RCTComponent.h" +#import "RCTMapboxGL.h" + +@class RCTBridge; + +@interface RCTMapboxAnnotation : MGLAnnotationView + +@property (nonatomic, weak, nullable) RCTMapboxGL *map; +@property (nonatomic, weak, nullable) RCTBridge *bridge; +/** + The center point (specified as a map coordinate) of the annotation. (required) + (read-only) + */ +@property (nonatomic) CLLocationCoordinate2D coordinate; + +/** + The string containing the annotation’s title. + + Although this property is optional, if you support the selection of annotations + in your map view, you are expected to provide this property. This string is + displayed in the callout for the associated annotation. + */ +@property (nonatomic, copy, nullable) NSString *title; + +/** + The string containing the annotation’s subtitle. + + This string is displayed in the callout for the associated annotation. + */ +@property (nonatomic, copy, nullable) NSString *subtitle; + + +@end + + diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotation.m b/ios/RCTMapboxGL/RCTMapboxAnnotation.m new file mode 100644 index 000000000..12eae3999 --- /dev/null +++ b/ios/RCTMapboxGL/RCTMapboxAnnotation.m @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTMapboxAnnotation.h" + +#import "RCTEventDispatcher.h" +#import "UIView+React.h" +#import "RCTBridge.h" +#import "RCTUtils.h" + +@implementation RCTMapboxAnnotation { +} +-(void)layoutSubviews { + [super layoutSubviews]; + [self.map layoutIfNeeded]; +} + +@end diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotationManager.h b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.h new file mode 100644 index 000000000..ec86dd2c1 --- /dev/null +++ b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewManager.h" + +@interface RCTMapboxAnnotationManager : RCTViewManager + +@end diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m new file mode 100644 index 000000000..345addd2b --- /dev/null +++ b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTMapboxAnnotationManager.h" + +#import "RCTUIManager.h" +#import "RCTConvert+CoreLocation.h" +#import "UIView+React.h" +#import "RCTMapboxAnnotation.h" + +@interface RCTMapboxAnnotationManager () + +@end + +@implementation RCTMapboxAnnotationManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + RCTMapboxAnnotation *marker = [RCTMapboxAnnotation new]; +// UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTap:)]; +// // setting this to NO allows the parent MapView to continue receiving marker selection events +// tapGestureRecognizer.cancelsTouchesInView = NO; +// [marker addGestureRecognizer:tapGestureRecognizer]; + marker.bridge = self.bridge; + return marker; +} + + +RCT_EXPORT_VIEW_PROPERTY(title, NSString) +RCT_EXPORT_VIEW_PROPERTY(subtitle, NSString) +RCT_EXPORT_VIEW_PROPERTY(coordinate, CLLocationCoordinate2D) + +@end diff --git a/ios/RCTMapboxGL/RCTMapboxGL.m b/ios/RCTMapboxGL/RCTMapboxGL.m index 569097a7f..a6c2a38b9 100644 --- a/ios/RCTMapboxGL/RCTMapboxGL.m +++ b/ios/RCTMapboxGL/RCTMapboxGL.m @@ -12,6 +12,7 @@ #import "UIView+React.h" #import "RCTLog.h" #import "RCTMapboxGLConversions.h" +#import "RCTMapboxAnnotation.h" @implementation RCTMapboxGL { /* Required to publish events */ @@ -42,6 +43,16 @@ @implementation RCTMapboxGL { MGLAnnotationVerticalAlignment _userLocationVerticalAlignment; /* So we don't fire onChangeUserTracking mode when triggered by props */ BOOL _isChangingUserTracking; + // Array to manually track RN subviews + // + // AIRMap implicitly creates subviews that aren't regular RN children + // (SMCalloutView injects an overlay subview), which otherwise confuses RN + // during component re-renders: + // https://github.com/facebook/react-native/blob/v0.16.0/React/Modules/RCTUIManager.m#L657 + // + // Implementation based on RCTTextField, another component with indirect children + // https://github.com/facebook/react-native/blob/v0.16.0/Libraries/Text/RCTTextField.m#L20 + NSMutableArray *_reactSubviews; } // View creation @@ -122,8 +133,35 @@ - (void)layoutSubviews [self createMapIfNeeded]; } _map.frame = self.bounds; + [_map layoutIfNeeded]; } +// React subviews for custom annotation management +- (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex { + // Our desired API is to pass up markers/overlays as children to the mapview component. + // This is where we intercept them and do the appropriate underlying mapview action. + if ([subview isKindOfClass:[RCTMapboxAnnotation class]]) { + ((RCTMapboxAnnotation *) subview).map = self; + [_map addAnnotation:(id ) subview]; + } + [_reactSubviews insertObject:(UIView *)subview atIndex:(NSUInteger) atIndex]; +} + +- (void)removeReactSubview:(id)subview { + // similarly, when the children are being removed we have to do the appropriate + // underlying mapview action here. + if ([subview isKindOfClass:[RCTMapboxAnnotation class]]) { + [self removeAnnotation:(id)subview]; + } + [_reactSubviews removeObject:(UIView *)subview]; +} + +- (NSArray> *)reactSubviews { + return _reactSubviews; +} + + + // Annotation management - (void)upsertAnnotation:(RCTMGLAnnotation *) annotation { @@ -203,6 +241,13 @@ - (BOOL)mapView:(RCTMapboxGL *)mapView annotationCanShowCallout:(id )annotation { + if ([annotation isKindOfClass:[RCTMapboxAnnotation class]]){ + return annotation; + } + return nil; +} + - (UIButton *)mapView:(MGLMapView *)mapView rightCalloutAccessoryViewForAnnotation:(id )annotation; { if ([annotation isKindOfClass:[RCTMGLAnnotation class]]) { From 0901d19ed22dfe4cc3d8b356d1ec4968e6c58437 Mon Sep 17 00:00:00 2001 From: Pavlo Aksonov Date: Thu, 22 Sep 2016 20:11:50 +0200 Subject: [PATCH 2/5] review fixes --- MapboxAnnotation.js | 283 ------------------- index.js | 2 +- ios/RCTMapboxGL/RCTMapboxAnnotation.h | 9 - ios/RCTMapboxGL/RCTMapboxAnnotationManager.m | 2 +- 4 files changed, 2 insertions(+), 294 deletions(-) delete mode 100644 MapboxAnnotation.js diff --git a/MapboxAnnotation.js b/MapboxAnnotation.js deleted file mode 100644 index 2b124d4c0..000000000 --- a/MapboxAnnotation.js +++ /dev/null @@ -1,283 +0,0 @@ -import React, { PropTypes } from 'react'; -import { - View, - requireNativeComponent, - StyleSheet, - Platform, - NativeModules, - Animated, - findNodeHandle, -} from 'react-native'; - -import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; - -const viewConfig = { - uiViewClassName: 'RCTMapboxAnnotation', - validAttributes: { - coordinate: true, - }, -}; - -const propTypes = { - ...View.propTypes, - - // TODO(lmr): get rid of these? - identifier: PropTypes.string, - reuseIdentifier: PropTypes.string, - - /** - * The title of the marker. This is only used if the component has no children that - * are an ``, in which case the default callout behavior will be used, which - * will show both the `title` and the `description`, if provided. - */ - title: PropTypes.string, - - /** - * The title of the marker. This is only used if the component has no children that - * are an ``, in which case the default callout behavior will be used, which - * will show both the `title` and the `description`, if provided. - */ - subtitle: PropTypes.string, - - /** - * The description of the marker. This is only used if the component has no children - * that are an ``, in which case the default callout behavior will be used, - * which will show both the `title` and the `description`, if provided. - */ - description: PropTypes.string, - - /** - * A custom image to be used as the marker's icon. Only local image resources are allowed to be - * used. - */ - image: PropTypes.any, - - /** - * If no custom marker view or custom image is provided, the platform default pin will be used, - * which can be customized by this color. Ignored if a custom marker is being used. - */ - pinColor: PropTypes.string, - - /** - * The coordinate for the marker. - */ - coordinate: PropTypes.shape({ - /** - * Coordinates for the anchor point of the marker. - */ - latitude: PropTypes.number.isRequired, - longitude: PropTypes.number.isRequired, - }).isRequired, - - /** - * The offset (in points) at which to display the view. - * - * By default, the center point of an annotation view is placed at the coordinate point of the - * associated annotation. You can use this property to reposition the annotation view as - * needed. This x and y offset values are measured in points. Positive offset values move the - * annotation view down and to the right, while negative values move it up and to the left. - * - * For android, see the `anchor` prop. - * - * @platform ios - */ - centerOffset: PropTypes.shape({ - /** - * Offset from the anchor point - */ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), - - /** - * The offset (in points) at which to place the callout bubble. - * - * This property determines the additional distance by which to move the callout bubble. When - * this property is set to (0, 0), the anchor point of the callout bubble is placed on the - * top-center point of the marker view’s frame. Specifying positive offset values moves the - * callout bubble down and to the right, while specifying negative values moves it up and to - * the left. - * - * For android, see the `calloutAnchor` prop. - * - * @platform ios - */ - calloutOffset: PropTypes.shape({ - /** - * Offset to the callout - */ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), - - /** - * Sets the anchor point for the marker. - * - * The anchor specifies the point in the icon image that is anchored to the marker's position - * on the Earth's surface. - * - * The anchor point is specified in the continuous space [0.0, 1.0] x [0.0, 1.0], where (0, 0) - * is the top-left corner of the image, and (1, 1) is the bottom-right corner. The anchoring - * point in a W x H image is the nearest discrete grid point in a (W + 1) x (H + 1) grid, - * obtained by scaling the then rounding. For example, in a 4 x 2 image, the anchor point - * (0.7, 0.6) resolves to the grid point at (3, 1). - * - * For ios, see the `centerOffset` prop. - * - * @platform android - */ - anchor: PropTypes.shape({ - /** - * Offset to the callout - */ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), - - /** - * Specifies the point in the marker image at which to anchor the callout when it is displayed. - * This is specified in the same coordinate system as the anchor. See the `andor` prop for more - * details. - * - * The default is the top middle of the image. - * - * For ios, see the `calloutOffset` prop. - * - * @platform android - */ - calloutAnchor: PropTypes.shape({ - /** - * Offset to the callout - */ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), - - /** - * Sets whether this marker should be flat against the map true or a billboard facing the - * camera false. - * - * @platform android - */ - flat: PropTypes.bool, - - draggable: PropTypes.bool, - - /** - * Callback that is called when the user presses on the marker - */ - onPress: PropTypes.func, - - /** - * Callback that is called when the user selects the marker, before the callout is shown. - * - * @platform ios - */ - onSelect: PropTypes.func, - - /** - * Callback that is called when the marker is deselected, before the callout is hidden. - * - * @platform ios - */ - onDeselect: PropTypes.func, - - /** - * Callback that is called when the user taps the callout view. - */ - onCalloutPress: PropTypes.func, - - /** - * Callback that is called when the user initiates a drag on this marker (if it is draggable) - */ - onDragStart: PropTypes.func, - - /** - * Callback called continuously as the marker is dragged - */ - onDrag: PropTypes.func, - - /** - * Callback that is called when a drag on this marker finishes. This is usually the point you - * will want to setState on the marker's coordinate again - */ - onDragEnd: PropTypes.func, -}; - -const defaultProps = { - onPress() {}, -}; - -class MapboxAnnotation extends React.Component { - constructor(props) { - super(props); - - this.showCallout = this.showCallout.bind(this); - this.hideCallout = this.hideCallout.bind(this); - } - - showCallout() { - this._runCommand('showCallout', []); - } - - hideCallout() { - this._runCommand('hideCallout', []); - } - - _getHandle() { - return findNodeHandle(this.marker); - } - - _runCommand(name, args) { - switch (Platform.OS) { - case 'android': - NativeModules.UIManager.dispatchViewManagerCommand( - this._getHandle(), - NativeModules.UIManager.RCTMapboxAnnotation.Commands[name], - args - ); - break; - - case 'ios': - NativeModules.RCTMapboxAnnotationManager[name].apply( - NativeModules.RCTMapboxAnnotationManager[name], - [this._getHandle(), ...args] - ); - break; - - default: - break; - } - } - - render() { - let image; - if (this.props.image) { - image = resolveAssetSource(this.props.image) || {}; - image = image.uri; - } - - return ( - { this.marker = ref; }} - {...this.props} - image={image} - style={[styles.marker, this.props.style]} - /> - ); - } -} - -MapboxAnnotation.propTypes = propTypes; -MapboxAnnotation.defaultProps = defaultProps; -MapboxAnnotation.viewConfig = viewConfig; - -const styles = StyleSheet.create({ - marker: { - position: 'absolute', - backgroundColor: 'transparent', - }, -}); - -const RCTMapboxAnnotation = requireNativeComponent('RCTMapboxAnnotation', MapboxAnnotation); -module.exports = MapboxAnnotation; diff --git a/index.js b/index.js index a0811a98e..4e55dd651 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ import { import cloneDeep from 'lodash/cloneDeep'; import clone from 'lodash/clone'; import isEqual from 'lodash/isEqual'; -import Annotation from './MapboxAnnotation'; +import Annotation from './Annotation'; const { MapboxGLManager } = NativeModules; const { mapStyles, userTrackingMode, userLocationVerticalAlignment, unknownResourceCount } = MapboxGLManager; diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotation.h b/ios/RCTMapboxGL/RCTMapboxAnnotation.h index 742cc765a..484e0c1e1 100644 --- a/ios/RCTMapboxGL/RCTMapboxAnnotation.h +++ b/ios/RCTMapboxGL/RCTMapboxAnnotation.h @@ -1,12 +1,3 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - #import "RCTMapboxAnnotation.h" #import diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m index 345addd2b..10b8ce02f 100644 --- a/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m +++ b/ios/RCTMapboxGL/RCTMapboxAnnotationManager.m @@ -24,7 +24,7 @@ @implementation RCTMapboxAnnotationManager - (UIView *)view { - RCTMapboxAnnotation *marker = [RCTMapboxAnnotation new]; + RCTMapboxAnnotation *marker = [[RCTMapboxAnnotation alloc] init]; // UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTap:)]; // // setting this to NO allows the parent MapView to continue receiving marker selection events // tapGestureRecognizer.cancelsTouchesInView = NO; From eb18cd0ed150304fe0eff548c39a263bfa624a28 Mon Sep 17 00:00:00 2001 From: Pavlo Aksonov Date: Fri, 23 Sep 2016 11:22:45 +0200 Subject: [PATCH 3/5] layoutIfNeeded doesn't display annotation sometimes --- ios/RCTMapboxGL/RCTMapboxAnnotation.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotation.m b/ios/RCTMapboxGL/RCTMapboxAnnotation.m index 12eae3999..2fdd5214d 100644 --- a/ios/RCTMapboxGL/RCTMapboxAnnotation.m +++ b/ios/RCTMapboxGL/RCTMapboxAnnotation.m @@ -18,7 +18,7 @@ @implementation RCTMapboxAnnotation { } -(void)layoutSubviews { [super layoutSubviews]; - [self.map layoutIfNeeded]; + [self.map layoutSubviews]; } @end From a89a62b791bc3f77ba859e48b27ea3638acefa94 Mon Sep 17 00:00:00 2001 From: Pavlo Aksonov Date: Mon, 26 Sep 2016 11:38:44 +0200 Subject: [PATCH 4/5] fix small issues --- ios/RCTMapboxGL/RCTMapboxAnnotation.m | 4 ---- ios/RCTMapboxGL/RCTMapboxGL.m | 11 ++++++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ios/RCTMapboxGL/RCTMapboxAnnotation.m b/ios/RCTMapboxGL/RCTMapboxAnnotation.m index 2fdd5214d..57471f3d5 100644 --- a/ios/RCTMapboxGL/RCTMapboxAnnotation.m +++ b/ios/RCTMapboxGL/RCTMapboxAnnotation.m @@ -16,9 +16,5 @@ @implementation RCTMapboxAnnotation { } --(void)layoutSubviews { - [super layoutSubviews]; - [self.map layoutSubviews]; -} @end diff --git a/ios/RCTMapboxGL/RCTMapboxGL.m b/ios/RCTMapboxGL/RCTMapboxGL.m index a6c2a38b9..dc954835c 100644 --- a/ios/RCTMapboxGL/RCTMapboxGL.m +++ b/ios/RCTMapboxGL/RCTMapboxGL.m @@ -64,6 +64,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _clipsToBounds = YES; _finishedLoading = NO; _annotations = [NSMutableDictionary dictionary]; + _reactSubviews = [NSMutableArray array]; } return self; @@ -119,6 +120,10 @@ - (void)createMapIfNeeded } [self addSubview:_map]; + for (UIView *annotations in _reactSubviews) { + [_map addAnnotation:annotations]; + } + [self layoutSubviews]; } @@ -133,7 +138,7 @@ - (void)layoutSubviews [self createMapIfNeeded]; } _map.frame = self.bounds; - [_map layoutIfNeeded]; + [_map layoutSubviews]; } // React subviews for custom annotation management @@ -151,7 +156,7 @@ - (void)removeReactSubview:(id)subview { // similarly, when the children are being removed we have to do the appropriate // underlying mapview action here. if ([subview isKindOfClass:[RCTMapboxAnnotation class]]) { - [self removeAnnotation:(id)subview]; + [_map removeAnnotation:(id)subview]; } [_reactSubviews removeObject:(UIView *)subview]; } @@ -242,7 +247,7 @@ - (BOOL)mapView:(RCTMapboxGL *)mapView annotationCanShowCallout:(id )annotation { - if ([annotation isKindOfClass:[RCTMapboxAnnotation class]]){ + if (![annotation isKindOfClass:[RCTMGLAnnotation class]] ){ return annotation; } return nil; From f65c96d4c1a68c17e59c9476fcd2427d3fc7815c Mon Sep 17 00:00:00 2001 From: Pavel Aksonov Date: Mon, 26 Sep 2016 18:03:11 +0200 Subject: [PATCH 5/5] Update API.md Add custom annotation note --- API.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/API.md b/API.md index d6e226dd9..9d839ad48 100644 --- a/API.md +++ b/API.md @@ -43,6 +43,8 @@ import { MapView } from 'react-native-mapbox-gl'; | `contentInset` | `array` | Optional | Change the padding of the viewport of the map. Offset is in pixels. `[top, right, bottom, left]` `[0, 0, 0, 0]` | | `style` | React styles | Optional | Styles the actual map view container | N/A | | `debugActive` | `boolean` | Optional | Turns on debug mode. | `false` | +| `children` | `array` | Optional | An array of custom Annotation views (iOS only). You must import Annotation view from the component and put your custom React Native view inside | null | + ## Callback props