Permalink
Browse files

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
  • Loading branch information...
nicklockwood authored and facebook-github-bot-5 committed Nov 26, 2015
1 parent 63ef826 commit 5b5b55027b1c63f4b1979a5af789f350898bdce5
@@ -149,9 +149,6 @@ var MapViewExample = React.createClass({
getInitialState() {
return {
mapRegion: null,
mapRegionInput: null,
annotations: null,
isFirstLoad: true,
};
},
@@ -163,12 +160,12 @@ var MapViewExample = React.createClass({
style={styles.map}
onRegionChange={this._onRegionChange}
onRegionChangeComplete={this._onRegionChangeComplete}
region={this.state.mapRegion || undefined}
annotations={this.state.annotations || undefined}
region={this.state.mapRegion}
annotations={this.state.annotations}
/>
<MapRegionInput
onChange={this._onRegionInputChanged}
region={this.state.mapRegionInput || undefined}
region={this.state.mapRegionInput}
/>
</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({
map: {
height: 150,
@@ -249,5 +354,23 @@ exports.examples = [
render() {
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';
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var Image = require('Image');
var NativeMethodsMixin = require('NativeMethodsMixin');
var PinColors = require('NativeModules').UIManager.RCTMap.Constants.PinColors;
var Platform = require('Platform');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
@@ -21,6 +23,8 @@ var View = require('View');
var deepDiffer = require('deepDiffer');
var insetsDiffer = require('insetsDiffer');
var merge = require('merge');
var processColor = require('processColor');
var resolveAssetSource = require('resolveAssetSource');
var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
@@ -194,7 +198,21 @@ var MapView = React.createClass({
/**
* 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,47 +253,79 @@ var MapView = React.createClass({
active: React.PropTypes.bool,
},
_onChange: function(event: Event) {
if (event.nativeEvent.continuous) {
this.props.onRegionChange &&
this.props.onRegionChange(event.nativeEvent.region);
} else {
this.props.onRegionChangeComplete &&
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);
}
render: function() {
if (event.nativeEvent.action === 'callout-click') {
if (!this.props.annotations) {
return;
}
let {annotations} = this.props;
annotations = annotations && annotations.map((annotation: Object) => {
let {tintColor, image} = annotation;
return {
...annotation,
tintColor: tintColor && processColor(tintColor),
image: image && resolveAssetSource(image),
};
});
// Find the annotation with the id of what has been pressed
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);
// 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) {
this.props.onRegionChange &&
this.props.onRegionChange(event.nativeEvent.region);
} else {
this.props.onRegionChangeComplete &&
this.props.onRegionChangeComplete(event.nativeEvent.region);
}
};
}
},
render: function() {
return <RCTMap {...this.props} onPress={this._onPress} onChange={this._onChange} />;
return (
<RCTMap
{...this.props}
annotations={annotations}
onPress={onPress}
onChange={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, {
nativeOnly: {onChange: true, onPress: true}
});
View
@@ -397,20 +397,26 @@ + (type)type:(id)json \
+ (UIColor *)UIColor:(id)json
{
if (!json) {
return nil;
}
if ([json isKindOfClass:[NSArray class]]) {
NSArray *components = [self NSNumberArray:json];
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
return [UIColor colorWithRed:[self CGFloat:components[0]]
green:[self CGFloat:components[1]]
blue:[self CGFloat:components[2]]
alpha:alpha];
} else {
} else if ([json isKindOfClass:[NSNumber class]]) {
NSUInteger argb = [self NSUInteger:json];
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
} else {
RCTLogConvertError(json, @"a color");
return nil;
}
}
@@ -460,12 +466,9 @@ + (UIImage *)UIImage:(id)json
NSURL *URL = [self NSURL:path];
NSString *scheme = URL.scheme.lowercaseString;
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);
image = [UIImage imageNamed:assetName];
} else {
NSString *assetName = RCTBundlePathForURL(URL);
image = [UIImage imageNamed:assetName];
if (!image) {
// Attempt to load from the file system
NSString *filePath = URL.path;
if (filePath.pathExtension.length == 0) {
View
@@ -84,6 +84,9 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
RCT_EXTERN id RCTNilIfNull(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
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
RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL);
// Converts a CGColor to a hex string
RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color);
Oops, something went wrong.

0 comments on commit 5b5b550

Please sign in to comment.