Added support for custom color and image for map annotations
Summary: public This diff extends RCTMap annotations with an `image` and `tintColor` property, which can be used to render completely custom pin graphics. The tintColor applies to both regular pins and custom pin images, allowing you to provide varied pin colors without needing multiple graphic assets. Reviewed By: fredliu Differential Revision: D2685581 fb-gh-sync-id: c7cf0af5c90fd8d1e9b3fec4b89206440b47ba8f
This commit is contained in:
parent
63ef826d44
commit
5b5b55027b
|
@ -149,9 +149,6 @@ var MapViewExample = React.createClass({
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
mapRegion: null,
|
|
||||||
mapRegionInput: null,
|
|
||||||
annotations: null,
|
|
||||||
isFirstLoad: true,
|
isFirstLoad: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -163,12 +160,12 @@ var MapViewExample = React.createClass({
|
||||||
style={styles.map}
|
style={styles.map}
|
||||||
onRegionChange={this._onRegionChange}
|
onRegionChange={this._onRegionChange}
|
||||||
onRegionChangeComplete={this._onRegionChangeComplete}
|
onRegionChangeComplete={this._onRegionChangeComplete}
|
||||||
region={this.state.mapRegion || undefined}
|
region={this.state.mapRegion}
|
||||||
annotations={this.state.annotations || undefined}
|
annotations={this.state.annotations}
|
||||||
/>
|
/>
|
||||||
<MapRegionInput
|
<MapRegionInput
|
||||||
onChange={this._onRegionInputChanged}
|
onChange={this._onRegionInputChanged}
|
||||||
region={this.state.mapRegionInput || undefined}
|
region={this.state.mapRegionInput}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -208,6 +205,114 @@ var MapViewExample = React.createClass({
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var CalloutMapViewExample = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
isFirstLoad: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.isFirstLoad) {
|
||||||
|
var onRegionChangeComplete = (region) => {
|
||||||
|
this.setState({
|
||||||
|
isFirstLoad: false,
|
||||||
|
annotations: [{
|
||||||
|
longitude: region.longitude,
|
||||||
|
latitude: region.latitude,
|
||||||
|
title: 'More Info...',
|
||||||
|
hasRightCallout: true,
|
||||||
|
onRightCalloutPress: () => {
|
||||||
|
alert('You Are Here');
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MapView
|
||||||
|
style={styles.map}
|
||||||
|
onRegionChangeComplete={onRegionChangeComplete}
|
||||||
|
region={this.state.mapRegion}
|
||||||
|
annotations={this.state.annotations}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var CustomPinColorMapViewExample = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
isFirstLoad: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.isFirstLoad) {
|
||||||
|
var onRegionChangeComplete = (region) => {
|
||||||
|
this.setState({
|
||||||
|
isFirstLoad: false,
|
||||||
|
annotations: [{
|
||||||
|
longitude: region.longitude,
|
||||||
|
latitude: region.latitude,
|
||||||
|
title: 'You Are Purple',
|
||||||
|
tintColor: MapView.PinColors.PURPLE,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MapView
|
||||||
|
style={styles.map}
|
||||||
|
onRegionChangeComplete={onRegionChangeComplete}
|
||||||
|
region={this.state.mapRegion}
|
||||||
|
annotations={this.state.annotations}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var CustomPinImageMapViewExample = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
isFirstLoad: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.isFirstLoad) {
|
||||||
|
var onRegionChangeComplete = (region) => {
|
||||||
|
this.setState({
|
||||||
|
isFirstLoad: false,
|
||||||
|
annotations: [{
|
||||||
|
longitude: region.longitude,
|
||||||
|
latitude: region.latitude,
|
||||||
|
title: 'Thumbs Up!',
|
||||||
|
image: require('image!uie_thumb_big'),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MapView
|
||||||
|
style={styles.map}
|
||||||
|
onRegionChangeComplete={onRegionChangeComplete}
|
||||||
|
region={this.state.mapRegion}
|
||||||
|
annotations={this.state.annotations}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
var styles = StyleSheet.create({
|
var styles = StyleSheet.create({
|
||||||
map: {
|
map: {
|
||||||
height: 150,
|
height: 150,
|
||||||
|
@ -249,5 +354,23 @@ exports.examples = [
|
||||||
render() {
|
render() {
|
||||||
return <MapView style={styles.map} showsUserLocation={true} />;
|
return <MapView style={styles.map} showsUserLocation={true} />;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Callout example',
|
||||||
|
render() {
|
||||||
|
return <CalloutMapViewExample style={styles.map} />;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Custom pin color',
|
||||||
|
render() {
|
||||||
|
return <CustomPinColorMapViewExample style={styles.map} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Custom pin image',
|
||||||
|
render() {
|
||||||
|
return <CustomPinImageMapViewExample style={styles.map} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||||
|
var Image = require('Image');
|
||||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||||
|
var PinColors = require('NativeModules').UIManager.RCTMap.Constants.PinColors;
|
||||||
var Platform = require('Platform');
|
var Platform = require('Platform');
|
||||||
var React = require('React');
|
var React = require('React');
|
||||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||||
|
@ -21,6 +23,8 @@ var View = require('View');
|
||||||
var deepDiffer = require('deepDiffer');
|
var deepDiffer = require('deepDiffer');
|
||||||
var insetsDiffer = require('insetsDiffer');
|
var insetsDiffer = require('insetsDiffer');
|
||||||
var merge = require('merge');
|
var merge = require('merge');
|
||||||
|
var processColor = require('processColor');
|
||||||
|
var resolveAssetSource = require('resolveAssetSource');
|
||||||
var requireNativeComponent = require('requireNativeComponent');
|
var requireNativeComponent = require('requireNativeComponent');
|
||||||
|
|
||||||
type Event = Object;
|
type Event = Object;
|
||||||
|
@ -194,7 +198,21 @@ var MapView = React.createClass({
|
||||||
/**
|
/**
|
||||||
* annotation id
|
* annotation id
|
||||||
*/
|
*/
|
||||||
id: React.PropTypes.string
|
id: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pin color. This can be any valid color string, or you can use one
|
||||||
|
* of the predefined PinColors constants. Applies to both standard pins
|
||||||
|
* and custom pin images.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
tintColor: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom pin image. This must be a static image resource inside the app.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
image: Image.propTypes.source,
|
||||||
|
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
@ -235,7 +253,47 @@ var MapView = React.createClass({
|
||||||
active: React.PropTypes.bool,
|
active: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
_onChange: function(event: Event) {
|
render: function() {
|
||||||
|
|
||||||
|
let {annotations} = this.props;
|
||||||
|
annotations = annotations && annotations.map((annotation: Object) => {
|
||||||
|
let {tintColor, image} = annotation;
|
||||||
|
return {
|
||||||
|
...annotation,
|
||||||
|
tintColor: tintColor && processColor(tintColor),
|
||||||
|
image: image && resolveAssetSource(image),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: these should be separate events, to reduce bridge traffic
|
||||||
|
if (annotations || this.props.onAnnotationPress) {
|
||||||
|
var onPress = (event: Event) => {
|
||||||
|
if (event.nativeEvent.action === 'annotation-click') {
|
||||||
|
this.props.onAnnotationPress &&
|
||||||
|
this.props.onAnnotationPress(event.nativeEvent.annotation);
|
||||||
|
} else if (event.nativeEvent.action === 'callout-click') {
|
||||||
|
// Find the annotation with the id that was pressed
|
||||||
|
for (let i = 0, l = annotations.length; i < l; i++) {
|
||||||
|
let annotation = annotations[i];
|
||||||
|
if (annotation.id === event.nativeEvent.annotationId) {
|
||||||
|
// Pass the right function
|
||||||
|
if (event.nativeEvent.side === 'left') {
|
||||||
|
annotation.onLeftCalloutPress &&
|
||||||
|
annotation.onLeftCalloutPress(event.nativeEvent);
|
||||||
|
} else if (event.nativeEvent.side === 'right') {
|
||||||
|
annotation.onRightCalloutPress &&
|
||||||
|
annotation.onRightCalloutPress(event.nativeEvent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: these should be separate events, to reduce bridge traffic
|
||||||
|
if (this.props.onRegionChange || this.props.onRegionChangeComplete) {
|
||||||
|
var onChange = (event: Event) => {
|
||||||
if (event.nativeEvent.continuous) {
|
if (event.nativeEvent.continuous) {
|
||||||
this.props.onRegionChange &&
|
this.props.onRegionChange &&
|
||||||
this.props.onRegionChange(event.nativeEvent.region);
|
this.props.onRegionChange(event.nativeEvent.region);
|
||||||
|
@ -243,39 +301,31 @@ var MapView = React.createClass({
|
||||||
this.props.onRegionChangeComplete &&
|
this.props.onRegionChangeComplete &&
|
||||||
this.props.onRegionChangeComplete(event.nativeEvent.region);
|
this.props.onRegionChangeComplete(event.nativeEvent.region);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
_onPress: function(event: Event) {
|
|
||||||
if (event.nativeEvent.action === 'annotation-click') {
|
|
||||||
this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.nativeEvent.action === 'callout-click') {
|
return (
|
||||||
if (!this.props.annotations) {
|
<RCTMap
|
||||||
return;
|
{...this.props}
|
||||||
}
|
annotations={annotations}
|
||||||
|
onPress={onPress}
|
||||||
// Find the annotation with the id of what has been pressed
|
onChange={onChange}
|
||||||
for (var i = 0; i < this.props.annotations.length; i++) {
|
/>
|
||||||
var annotation = this.props.annotations[i];
|
);
|
||||||
if (annotation.id === event.nativeEvent.annotationId) {
|
|
||||||
// Pass the right function
|
|
||||||
if (event.nativeEvent.side === 'left') {
|
|
||||||
annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent);
|
|
||||||
} else if (event.nativeEvent.side === 'right') {
|
|
||||||
annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return <RCTMap {...this.props} onPress={this._onPress} onChange={this._onChange} />;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard iOS MapView pin color constants, to be used with the
|
||||||
|
* `annotation.tintColor` property. You are not obliged to use these,
|
||||||
|
* but they are useful for matching the standard iOS look and feel.
|
||||||
|
*/
|
||||||
|
MapView.PinColors = PinColors && {
|
||||||
|
RED: PinColors.RED,
|
||||||
|
GREEN: PinColors.GREEN,
|
||||||
|
PURPLE: PinColors.PURPLE,
|
||||||
|
};
|
||||||
|
|
||||||
var RCTMap = requireNativeComponent('RCTMap', MapView, {
|
var RCTMap = requireNativeComponent('RCTMap', MapView, {
|
||||||
nativeOnly: {onChange: true, onPress: true}
|
nativeOnly: {onChange: true, onPress: true}
|
||||||
});
|
});
|
||||||
|
|
|
@ -397,6 +397,9 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
|
|
||||||
+ (UIColor *)UIColor:(id)json
|
+ (UIColor *)UIColor:(id)json
|
||||||
{
|
{
|
||||||
|
if (!json) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
if ([json isKindOfClass:[NSArray class]]) {
|
if ([json isKindOfClass:[NSArray class]]) {
|
||||||
NSArray *components = [self NSNumberArray:json];
|
NSArray *components = [self NSNumberArray:json];
|
||||||
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
|
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
|
||||||
|
@ -404,13 +407,16 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
green:[self CGFloat:components[1]]
|
green:[self CGFloat:components[1]]
|
||||||
blue:[self CGFloat:components[2]]
|
blue:[self CGFloat:components[2]]
|
||||||
alpha:alpha];
|
alpha:alpha];
|
||||||
} else {
|
} else if ([json isKindOfClass:[NSNumber class]]) {
|
||||||
NSUInteger argb = [self NSUInteger:json];
|
NSUInteger argb = [self NSUInteger:json];
|
||||||
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
|
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
|
||||||
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
|
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
|
||||||
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
|
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
|
||||||
CGFloat b = (argb & 0xFF) / 255.0;
|
CGFloat b = (argb & 0xFF) / 255.0;
|
||||||
return [UIColor colorWithRed:r green:g blue:b alpha:a];
|
return [UIColor colorWithRed:r green:g blue:b alpha:a];
|
||||||
|
} else {
|
||||||
|
RCTLogConvertError(json, @"a color");
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,12 +466,9 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
NSURL *URL = [self NSURL:path];
|
NSURL *URL = [self NSURL:path];
|
||||||
NSString *scheme = URL.scheme.lowercaseString;
|
NSString *scheme = URL.scheme.lowercaseString;
|
||||||
if ([scheme isEqualToString:@"file"]) {
|
if ([scheme isEqualToString:@"file"]) {
|
||||||
if (RCTIsXCAssetURL(URL)) {
|
|
||||||
// Image may reside inside a .car file, in which case we have no choice
|
|
||||||
// but to use +[UIImage imageNamed] - but this method isn't thread safe
|
|
||||||
NSString *assetName = RCTBundlePathForURL(URL);
|
NSString *assetName = RCTBundlePathForURL(URL);
|
||||||
image = [UIImage imageNamed:assetName];
|
image = [UIImage imageNamed:assetName];
|
||||||
} else {
|
if (!image) {
|
||||||
// Attempt to load from the file system
|
// Attempt to load from the file system
|
||||||
NSString *filePath = URL.path;
|
NSString *filePath = URL.path;
|
||||||
if (filePath.pathExtension.length == 0) {
|
if (filePath.pathExtension.length == 0) {
|
||||||
|
|
|
@ -84,6 +84,9 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
|
||||||
RCT_EXTERN id RCTNilIfNull(id value);
|
RCT_EXTERN id RCTNilIfNull(id value);
|
||||||
RCT_EXTERN id RCTNullIfNil(id value);
|
RCT_EXTERN id RCTNullIfNil(id value);
|
||||||
|
|
||||||
|
// Convert NaN or infinite values to zero, as these aren't JSON-safe
|
||||||
|
RCT_EXTERN double RCTZeroIfNaN(double value);
|
||||||
|
|
||||||
// Convert data to a Base64-encoded data URL
|
// Convert data to a Base64-encoded data URL
|
||||||
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
|
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
|
||||||
|
|
||||||
|
@ -96,3 +99,6 @@ RCT_EXTERN NSString *RCTBundlePathForURL(NSURL *URL);
|
||||||
|
|
||||||
// Determines if a given image URL actually refers to an XCAsset
|
// Determines if a given image URL actually refers to an XCAsset
|
||||||
RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL);
|
RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL);
|
||||||
|
|
||||||
|
// Converts a CGColor to a hex string
|
||||||
|
RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color);
|
||||||
|
|
|
@ -420,6 +420,11 @@ id RCTNilIfNull(id value)
|
||||||
return value == (id)kCFNull ? nil : value;
|
return value == (id)kCFNull ? nil : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RCT_EXTERN double RCTZeroIfNaN(double value)
|
||||||
|
{
|
||||||
|
return isnan(value) || isinf(value) ? 0 : value;
|
||||||
|
}
|
||||||
|
|
||||||
NSURL *RCTDataURL(NSString *mimeType, NSData *data)
|
NSURL *RCTDataURL(NSString *mimeType, NSData *data)
|
||||||
{
|
{
|
||||||
return [NSURL URLWithString:
|
return [NSURL URLWithString:
|
||||||
|
@ -511,3 +516,62 @@ BOOL RCTIsXCAssetURL(NSURL *imageURL)
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
|
||||||
|
{
|
||||||
|
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
|
||||||
|
const CGFloat *components = CGColorGetComponents(color);
|
||||||
|
switch (model)
|
||||||
|
{
|
||||||
|
case kCGColorSpaceModelMonochrome:
|
||||||
|
{
|
||||||
|
rgba[0] = components[0];
|
||||||
|
rgba[1] = components[0];
|
||||||
|
rgba[2] = components[0];
|
||||||
|
rgba[3] = components[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kCGColorSpaceModelRGB:
|
||||||
|
{
|
||||||
|
rgba[0] = components[0];
|
||||||
|
rgba[1] = components[1];
|
||||||
|
rgba[2] = components[2];
|
||||||
|
rgba[3] = components[3];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kCGColorSpaceModelCMYK:
|
||||||
|
case kCGColorSpaceModelDeviceN:
|
||||||
|
case kCGColorSpaceModelIndexed:
|
||||||
|
case kCGColorSpaceModelLab:
|
||||||
|
case kCGColorSpaceModelPattern:
|
||||||
|
case kCGColorSpaceModelUnknown:
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef RCT_DEBUG
|
||||||
|
//unsupported format
|
||||||
|
RCTLogError(@"Unsupported color model: %i", model);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
rgba[0] = 0.0;
|
||||||
|
rgba[1] = 0.0;
|
||||||
|
rgba[2] = 0.0;
|
||||||
|
rgba[3] = 1.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *RCTColorToHexString(CGColorRef color)
|
||||||
|
{
|
||||||
|
CGFloat rgba[4];
|
||||||
|
RCTGetRGBAColorComponents(color, rgba);
|
||||||
|
uint8_t r = rgba[0]*255;
|
||||||
|
uint8_t g = rgba[1]*255;
|
||||||
|
uint8_t b = rgba[2]*255;
|
||||||
|
uint8_t a = rgba[3]*255;
|
||||||
|
if (a < 255) {
|
||||||
|
return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a];
|
||||||
|
} else {
|
||||||
|
return [NSString stringWithFormat:@"#%02x%02x%02x", r, g, b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,11 @@ RCT_ENUM_CONVERTER(MKMapType, (@{
|
||||||
shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]];
|
shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]];
|
||||||
shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]];
|
shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]];
|
||||||
shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]];
|
shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]];
|
||||||
|
shape.tintColor = [RCTConvert UIColor:json[@"tintColor"]];
|
||||||
|
shape.image = [RCTConvert UIImage:json[@"image"]];
|
||||||
|
if (shape.tintColor && shape.image) {
|
||||||
|
shape.image = [shape.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||||
|
}
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#import "RCTConvert+MapKit.h"
|
#import "RCTConvert+MapKit.h"
|
||||||
#import "RCTEventDispatcher.h"
|
#import "RCTEventDispatcher.h"
|
||||||
#import "RCTMap.h"
|
#import "RCTMap.h"
|
||||||
|
#import "RCTUtils.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
#import "RCTPointAnnotation.h"
|
#import "RCTPointAnnotation.h"
|
||||||
|
|
||||||
|
@ -55,6 +56,17 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||||
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
|
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSDictionary<NSString *, id> *)constantsToExport
|
||||||
|
{
|
||||||
|
return @{
|
||||||
|
@"PinColors": @{
|
||||||
|
@"RED": RCTColorToHexString([MKPinAnnotationView redPinColor].CGColor),
|
||||||
|
@"GREEN": RCTColorToHexString([MKPinAnnotationView greenPinColor].CGColor),
|
||||||
|
@"PURPLE": RCTColorToHexString([MKPinAnnotationView purplePinColor].CGColor),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark MKMapViewDelegate
|
#pragma mark MKMapViewDelegate
|
||||||
|
|
||||||
- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
|
- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
|
||||||
|
@ -81,10 +93,41 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"RCTAnnotation"];
|
MKAnnotationView *annotationView;
|
||||||
|
if (annotation.image) {
|
||||||
|
if (annotation.tintColor) {
|
||||||
|
|
||||||
|
NSString *const reuseIdentifier = @"RCTImageViewAnnotation";
|
||||||
|
NSInteger imageViewTag = 99;
|
||||||
|
annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier];
|
||||||
|
if (!annotationView) {
|
||||||
|
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
|
||||||
|
UIImageView *imageView = [UIImageView new];
|
||||||
|
imageView.tag = imageViewTag;
|
||||||
|
[annotationView addSubview:imageView];
|
||||||
|
}
|
||||||
|
|
||||||
|
UIImageView *imageView = (UIImageView *)[annotationView viewWithTag:imageViewTag];
|
||||||
|
imageView.image = annotation.image;
|
||||||
|
imageView.tintColor = annotation.tintColor;
|
||||||
|
[imageView sizeToFit];
|
||||||
|
imageView.center = CGPointZero;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]);
|
||||||
|
annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
|
||||||
|
annotationView.image = annotation.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]);
|
||||||
|
annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
|
||||||
|
((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop;
|
||||||
|
((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor];
|
||||||
|
}
|
||||||
annotationView.canShowCallout = true;
|
annotationView.canShowCallout = true;
|
||||||
annotationView.animatesDrop = annotation.animateDrop;
|
|
||||||
|
|
||||||
annotationView.leftCalloutAccessoryView = nil;
|
annotationView.leftCalloutAccessoryView = nil;
|
||||||
if (annotation.hasLeftCallout) {
|
if (annotation.hasLeftCallout) {
|
||||||
|
@ -172,13 +215,17 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||||
BOOL needZoom = NO;
|
BOOL needZoom = NO;
|
||||||
CGFloat newLongitudeDelta = 0.0f;
|
CGFloat newLongitudeDelta = 0.0f;
|
||||||
MKCoordinateRegion region = mapView.region;
|
MKCoordinateRegion region = mapView.region;
|
||||||
// On iOS 7, it's possible that we observe invalid locations during initialization of the map.
|
|
||||||
// Filter those out.
|
// On iOS 7, it's possible that we observe invalid locations during
|
||||||
|
// initialization of the map. Filter those out.
|
||||||
if (!CLLocationCoordinate2DIsValid(region.center)) {
|
if (!CLLocationCoordinate2DIsValid(region.center)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time.
|
|
||||||
// So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound.
|
// Calculation on float is not 100% accurate. If user zoom to max/min and then
|
||||||
|
// move, it's likely the map will auto zoom to max/min from time to time.
|
||||||
|
// So let's try to make map zoom back to 99% max or 101% min so that there is
|
||||||
|
// some buffer, and moving the map won't constantly hit the max/min bound.
|
||||||
if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) {
|
if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) {
|
||||||
needZoom = YES;
|
needZoom = YES;
|
||||||
newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
|
newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
|
||||||
|
@ -204,15 +251,13 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
|
|
||||||
|
|
||||||
mapView.onChange(@{
|
mapView.onChange(@{
|
||||||
@"continuous": @(continuous),
|
@"continuous": @(continuous),
|
||||||
@"region": @{
|
@"region": @{
|
||||||
@"latitude": @(FLUSH_NAN(region.center.latitude)),
|
@"latitude": @(RCTZeroIfNaN(region.center.latitude)),
|
||||||
@"longitude": @(FLUSH_NAN(region.center.longitude)),
|
@"longitude": @(RCTZeroIfNaN(region.center.longitude)),
|
||||||
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
|
@"latitudeDelta": @(RCTZeroIfNaN(region.span.latitudeDelta)),
|
||||||
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
|
@"longitudeDelta": @(RCTZeroIfNaN(region.span.longitudeDelta)),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
@property (nonatomic, assign) BOOL hasLeftCallout;
|
@property (nonatomic, assign) BOOL hasLeftCallout;
|
||||||
@property (nonatomic, assign) BOOL hasRightCallout;
|
@property (nonatomic, assign) BOOL hasRightCallout;
|
||||||
@property (nonatomic, assign) BOOL animateDrop;
|
@property (nonatomic, assign) BOOL animateDrop;
|
||||||
|
@property (nonatomic, strong) UIColor *tintColor;
|
||||||
|
@property (nonatomic, strong) UIImage *image;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
Loading…
Reference in New Issue