mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-06 09:17:55 +08:00
Added ability to use a custom view for MapView annotations
Summary: public This diff adds the ability to specify a custom React component (aka view) to be displayed as a MapView pin. This makes it possible to use remote images (using an <Image/> component), or text (using a <Text/> component), or anything else. One consequence of this is that MapView can no longer support arbitrary subviews. To place views in front the map, add them to a separate container view. Reviewed By: tadeuzagallo Differential Revision: D2764790 fb-gh-sync-id: e16b44e866c2d76c76b0cb35ef9eefbfc68d6719
This commit is contained in:
committed by
facebook-github-bot-6
parent
97c75cf5a4
commit
f9dfb90a35
@@ -17,10 +17,12 @@
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
MapView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
@@ -239,14 +241,14 @@ var MapViewExample = React.createClass({
|
||||
|
||||
});
|
||||
|
||||
type CalloutMapViewExampleState = {
|
||||
type AnnotationExampleState = {
|
||||
isFirstLoad: boolean,
|
||||
annotations?: Annotations,
|
||||
mapRegion?: MapRegion,
|
||||
};
|
||||
var CalloutMapViewExample = React.createClass({
|
||||
var AnnotationExample = React.createClass({
|
||||
|
||||
getInitialState(): CalloutMapViewExampleState {
|
||||
getInitialState(): AnnotationExampleState {
|
||||
return {
|
||||
isFirstLoad: true,
|
||||
};
|
||||
@@ -260,11 +262,7 @@ var CalloutMapViewExample = React.createClass({
|
||||
annotations: [{
|
||||
longitude: region.longitude,
|
||||
latitude: region.latitude,
|
||||
title: 'More Info...',
|
||||
hasRightCallout: true,
|
||||
onRightCalloutPress: () => {
|
||||
alert('You Are Here');
|
||||
},
|
||||
...this.props.annotation,
|
||||
}],
|
||||
});
|
||||
};
|
||||
@@ -282,141 +280,6 @@ var CalloutMapViewExample = React.createClass({
|
||||
|
||||
});
|
||||
|
||||
type CustomPinColorMapViewExampleState = {
|
||||
isFirstLoad: boolean,
|
||||
annotations?: Annotations,
|
||||
mapRegion?: MapRegion,
|
||||
};
|
||||
var CustomPinColorMapViewExample = React.createClass({
|
||||
|
||||
getInitialState(): CustomPinColorMapViewExampleState {
|
||||
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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
type CustomPinImageMapViewExampleState = {
|
||||
isFirstLoad: boolean,
|
||||
annotations?: Annotations,
|
||||
mapRegion?: MapRegion,
|
||||
};
|
||||
var CustomPinImageMapViewExample = React.createClass({
|
||||
|
||||
getInitialState(): CustomPinImageMapViewExampleState {
|
||||
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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
type Overlays = Array<{
|
||||
coordinates?: Array<{
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
}>,
|
||||
lineWidth?: number,
|
||||
strokeColor?: string,
|
||||
fillColor?: string,
|
||||
id?: string,
|
||||
}>;
|
||||
type CustomOverlayMapViewExampleState = {
|
||||
isFirstLoad: boolean,
|
||||
overlays?: Overlays,
|
||||
annotations?: Annotations,
|
||||
mapRegion?: MapRegion,
|
||||
};
|
||||
var CustomOverlayMapViewExample = React.createClass({
|
||||
|
||||
getInitialState(): CustomOverlayMapViewExampleState {
|
||||
return {
|
||||
isFirstLoad: true,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.isFirstLoad) {
|
||||
var onRegionChangeComplete = (region) => {
|
||||
this.setState({
|
||||
isFirstLoad: false,
|
||||
overlays: [{
|
||||
coordinates:[
|
||||
{latitude: 32.47, longitude: -107.85},
|
||||
{latitude: 45.13, longitude: -94.48},
|
||||
{latitude: 39.27, longitude: -83.25},
|
||||
{latitude: 32.47, longitude: -107.85},
|
||||
],
|
||||
strokeColor: '#f007',
|
||||
lineWidth: 3,
|
||||
}],
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<MapView
|
||||
style={styles.map}
|
||||
onRegionChangeComplete={onRegionChangeComplete}
|
||||
region={this.state.mapRegion}
|
||||
overlays={this.state.overlays}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
map: {
|
||||
height: 150,
|
||||
@@ -451,36 +314,92 @@ exports.description = 'Base component to display maps';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Map',
|
||||
render(): ReactElement { return <MapViewExample />; }
|
||||
render() {
|
||||
return <MapViewExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Map shows user location',
|
||||
render() {
|
||||
return <MapView style={styles.map} showsUserLocation={true} />;
|
||||
return <MapView style={styles.map} showsUserLocation={true} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Callout example',
|
||||
render() {
|
||||
return <CalloutMapViewExample style={styles.map} />;
|
||||
return <AnnotationExample style={styles.map} annotation={{
|
||||
title: 'More Info...',
|
||||
rightCalloutView: (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
alert('You Are Here');
|
||||
}}>
|
||||
<Image
|
||||
style={{width:30, height:30}}
|
||||
source={require('image!uie_thumb_selected')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}/>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom pin color',
|
||||
render() {
|
||||
return <CustomPinColorMapViewExample style={styles.map} />;
|
||||
return <AnnotationExample style={styles.map} annotation={{
|
||||
title: 'You Are Purple',
|
||||
tintColor: MapView.PinColors.PURPLE,
|
||||
}}/>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom pin image',
|
||||
render() {
|
||||
return <CustomPinImageMapViewExample style={styles.map} />;
|
||||
return <AnnotationExample style={styles.map} annotation={{
|
||||
title: 'Thumbs Up!',
|
||||
image: require('image!uie_thumb_big'),
|
||||
}}/>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom pin view',
|
||||
render() {
|
||||
return <AnnotationExample style={styles.map} annotation={{
|
||||
title: 'Thumbs Up!',
|
||||
view: <View style={{
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Text style={{fontWeight: 'bold', color: '#f007'}}>
|
||||
Thumbs Up!
|
||||
</Text>
|
||||
<Image
|
||||
style={{width: 90, height: 65, resizeMode: 'cover'}}
|
||||
source={require('image!uie_thumb_big')}
|
||||
/>
|
||||
</View>,
|
||||
}}/>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom overlay',
|
||||
render() {
|
||||
return <CustomOverlayMapViewExample style={styles.map} />;
|
||||
return <MapView
|
||||
style={styles.map}
|
||||
region={{
|
||||
latitude: 39.06,
|
||||
longitude: -95.22,
|
||||
}}
|
||||
overlays={[{
|
||||
coordinates:[
|
||||
{latitude: 32.47, longitude: -107.85},
|
||||
{latitude: 45.13, longitude: -94.48},
|
||||
{latitude: 39.27, longitude: -83.25},
|
||||
{latitude: 32.47, longitude: -107.85},
|
||||
],
|
||||
strokeColor: '#f007',
|
||||
lineWidth: 3,
|
||||
}]}
|
||||
/>;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@@ -11,78 +11,31 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var Image = require('Image');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var Platform = require('Platform');
|
||||
var RCTMap = require('UIManager').RCTMap;
|
||||
var RCTMapConstants = RCTMap && RCTMap.Constants;
|
||||
var React = require('React');
|
||||
var View = require('View');
|
||||
const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
const Image = require('Image');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const Platform = require('Platform');
|
||||
const RCTMapConfig = require('UIManager').RCTMap;
|
||||
const RCTMapConstants = RCTMapConfig && RCTMapConfig.Constants;
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
var processColor = require('processColor');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
const processColor = require('processColor');
|
||||
const resolveAssetSource = require('resolveAssetSource');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
type Event = Object;
|
||||
type MapRegion = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
latitudeDelta: number;
|
||||
longitudeDelta: number;
|
||||
latitudeDelta?: ?number;
|
||||
longitudeDelta?: ?number;
|
||||
};
|
||||
|
||||
var MapView = React.createClass({
|
||||
const MapView = React.createClass({
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
||||
checkAnnotationIds: function (annotations: Array<Object>) {
|
||||
|
||||
var newAnnotations = annotations.map(function (annotation) {
|
||||
if (!annotation.id) {
|
||||
// TODO: add a base64 (or similar) encoder here
|
||||
annotation.id = encodeURIComponent(JSON.stringify(annotation));
|
||||
}
|
||||
return annotation;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
annotations: newAnnotations
|
||||
});
|
||||
},
|
||||
|
||||
checkOverlayIds: function (overlays: Array<Object>) {
|
||||
|
||||
var newOverlays = overlays.map(function (overlay) {
|
||||
if (!overlay.id) {
|
||||
// TODO: add a base64 (or similar) encoder here
|
||||
overlay.id = encodeURIComponent(JSON.stringify(overlay));
|
||||
}
|
||||
return overlay;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
overlays: newOverlays
|
||||
});
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
if (this.props.annotations) {
|
||||
this.checkAnnotationIds(this.props.annotations);
|
||||
}
|
||||
if (this.props.overlays) {
|
||||
this.checkOverlayIds(this.props.overlays);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps: Object) {
|
||||
if (nextProps.annotations) {
|
||||
this.checkAnnotationIds(nextProps.annotations);
|
||||
}
|
||||
if (nextProps.overlays) {
|
||||
this.checkOverlayIds(nextProps.overlays);
|
||||
}
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
...View.propTypes,
|
||||
/**
|
||||
@@ -151,6 +104,8 @@ var MapView = React.createClass({
|
||||
* - standard: standard road map (default)
|
||||
* - satellite: satellite view
|
||||
* - hybrid: satellite view with roads and points of interest overlaid
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
mapType: React.PropTypes.oneOf([
|
||||
'standard',
|
||||
@@ -175,12 +130,13 @@ var MapView = React.createClass({
|
||||
* Distance between the minimum and the maximum latitude/longitude
|
||||
* to be displayed.
|
||||
*/
|
||||
latitudeDelta: React.PropTypes.number.isRequired,
|
||||
longitudeDelta: React.PropTypes.number.isRequired,
|
||||
latitudeDelta: React.PropTypes.number,
|
||||
longitudeDelta: React.PropTypes.number,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Map annotations with title/subtitle.
|
||||
* @platform ios
|
||||
*/
|
||||
annotations: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
/**
|
||||
@@ -199,19 +155,14 @@ var MapView = React.createClass({
|
||||
*/
|
||||
title: React.PropTypes.string,
|
||||
subtitle: React.PropTypes.string,
|
||||
|
||||
|
||||
/**
|
||||
* Whether the Annotation has callout buttons.
|
||||
* Callout views.
|
||||
*/
|
||||
hasLeftCallout: React.PropTypes.bool,
|
||||
hasRightCallout: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Event handlers for callout buttons.
|
||||
*/
|
||||
onLeftCalloutPress: React.PropTypes.func,
|
||||
onRightCalloutPress: React.PropTypes.func,
|
||||
|
||||
leftCalloutView: React.PropTypes.element,
|
||||
rightCalloutView: React.PropTypes.element,
|
||||
detailCalloutView: React.PropTypes.element,
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -220,24 +171,40 @@ var MapView = React.createClass({
|
||||
* Note that on iOS 8 and earlier, only the standard PinColor constants
|
||||
* are supported for regular pins. For custom pin images, any tintColor
|
||||
* value is supported on all iOS versions.
|
||||
* @platform ios
|
||||
*/
|
||||
tintColor: React.PropTypes.string,
|
||||
tintColor: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.number
|
||||
]),
|
||||
|
||||
/**
|
||||
* Custom pin image. This must be a static image resource inside the app.
|
||||
* @platform ios
|
||||
*/
|
||||
image: Image.propTypes.source,
|
||||
|
||||
/**
|
||||
* Custom pin view. If set, this replaces the pin or custom pin image.
|
||||
*/
|
||||
view: React.PropTypes.element,
|
||||
|
||||
/**
|
||||
* annotation id
|
||||
*/
|
||||
id: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Deprecated. Use the left/right/detailsCalloutView props instead.
|
||||
*/
|
||||
hasLeftCallout: React.PropTypes.bool,
|
||||
hasRightCallout: React.PropTypes.bool,
|
||||
onLeftCalloutPress: React.PropTypes.func,
|
||||
onRightCalloutPress: React.PropTypes.func,
|
||||
|
||||
})),
|
||||
|
||||
/**
|
||||
* Map overlays
|
||||
* @platform ios
|
||||
*/
|
||||
overlays: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
/**
|
||||
@@ -252,8 +219,14 @@ var MapView = React.createClass({
|
||||
* Line attributes
|
||||
*/
|
||||
lineWidth: React.PropTypes.number,
|
||||
strokeColor: React.PropTypes.string,
|
||||
fillColor: React.PropTypes.string,
|
||||
strokeColor: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.number
|
||||
]),
|
||||
fillColor: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.number
|
||||
]),
|
||||
|
||||
/**
|
||||
* Overlay id
|
||||
@@ -263,17 +236,20 @@ var MapView = React.createClass({
|
||||
|
||||
/**
|
||||
* Maximum size of area that can be displayed.
|
||||
* @platform ios
|
||||
*/
|
||||
maxDelta: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* Minimum size of area that can be displayed.
|
||||
* @platform ios
|
||||
*/
|
||||
minDelta: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* Insets for the map's legal label, originally at bottom left of the map.
|
||||
* See `EdgeInsetsPropType.js` for more information.
|
||||
* @platform ios
|
||||
*/
|
||||
legalLabelInsets: EdgeInsetsPropType,
|
||||
|
||||
@@ -299,22 +275,81 @@ var MapView = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
let {annotations, overlays} = this.props;
|
||||
annotations = annotations && annotations.map((annotation: Object) => {
|
||||
let {tintColor, image} = annotation;
|
||||
let children = [], {annotations, overlays} = this.props;
|
||||
annotations = annotations && annotations.map((annotation: Object, index: number) => {
|
||||
let {
|
||||
id,
|
||||
image,
|
||||
tintColor,
|
||||
view,
|
||||
leftCalloutView,
|
||||
rightCalloutView,
|
||||
detailCalloutView,
|
||||
} = annotation;
|
||||
|
||||
if (!view && image && tintColor) {
|
||||
view = <Image
|
||||
style={{
|
||||
tintColor: processColor(tintColor),
|
||||
}}
|
||||
source={image}
|
||||
/>;
|
||||
}
|
||||
if (view) {
|
||||
var viewIndex = children.length;
|
||||
children.push(React.cloneElement(view, {
|
||||
style: [styles.annotationView, view.props.style || {}]
|
||||
}));
|
||||
}
|
||||
if (leftCalloutView) {
|
||||
var leftCalloutViewIndex = children.length;
|
||||
children.push(React.cloneElement(leftCalloutView, {
|
||||
style: [styles.calloutView, leftCalloutView.props.style || {}]
|
||||
}));
|
||||
}
|
||||
if (rightCalloutView) {
|
||||
var rightCalloutViewIndex = children.length;
|
||||
children.push(React.cloneElement(rightCalloutView, {
|
||||
style: [styles.calloutView, rightCalloutView.props.style || {}]
|
||||
}));
|
||||
}
|
||||
if (detailCalloutView) {
|
||||
var detailCalloutViewIndex = children.length;
|
||||
children.push(React.cloneElement(detailCalloutView, {
|
||||
style: [styles.calloutView, detailCalloutView.props.style || {}]
|
||||
}));
|
||||
}
|
||||
['hasLeftCallout', 'onLeftCalloutPress'].forEach(key => {
|
||||
if (annotation[key]) {
|
||||
console.warn('`' + key + '` is deprecated. Use leftCalloutView instead.');
|
||||
}
|
||||
});
|
||||
['hasRightCallout', 'onRightCalloutPress'].forEach(key => {
|
||||
if (annotation[key]) {
|
||||
console.warn('`' + key + '` is deprecated. Use rightCalloutView instead.');
|
||||
}
|
||||
});
|
||||
return {
|
||||
...annotation,
|
||||
tintColor: tintColor && processColor(tintColor),
|
||||
image: image && resolveAssetSource(image),
|
||||
viewIndex,
|
||||
leftCalloutViewIndex,
|
||||
rightCalloutViewIndex,
|
||||
detailCalloutViewIndex,
|
||||
view: undefined,
|
||||
leftCalloutView: undefined,
|
||||
rightCalloutView: undefined,
|
||||
detailCalloutView: undefined,
|
||||
id: id || String(index),
|
||||
};
|
||||
});
|
||||
overlays = overlays && overlays.map((overlay: Object) => {
|
||||
let {strokeColor, fillColor} = overlay;
|
||||
overlays = overlays && overlays.map((overlay: Object, index: number) => {
|
||||
let {id, fillColor, strokeColor} = overlay;
|
||||
return {
|
||||
...overlay,
|
||||
strokeColor: strokeColor && processColor(strokeColor),
|
||||
fillColor: fillColor && processColor(fillColor),
|
||||
id: id || String(index),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -364,6 +399,7 @@ var MapView = React.createClass({
|
||||
<RCTMap
|
||||
{...this.props}
|
||||
annotations={annotations}
|
||||
children={children}
|
||||
overlays={overlays}
|
||||
onPress={onPress}
|
||||
onChange={onChange}
|
||||
@@ -372,6 +408,17 @@ var MapView = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
annotationView: {
|
||||
position: 'absolute',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
calloutView: {
|
||||
position: 'absolute',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Standard iOS MapView pin color constants, to be used with the
|
||||
* `annotation.tintColor` property. On iOS 8 and earlier these are the
|
||||
@@ -379,14 +426,14 @@ var MapView = React.createClass({
|
||||
* you are not obliged to use these, but they are useful for matching
|
||||
* the standard iOS look and feel.
|
||||
*/
|
||||
let PinColors = RCTMapConstants && RCTMapConstants.PinColors;
|
||||
const PinColors = RCTMapConstants && RCTMapConstants.PinColors;
|
||||
MapView.PinColors = PinColors && {
|
||||
RED: PinColors.RED,
|
||||
GREEN: PinColors.GREEN,
|
||||
PURPLE: PinColors.PURPLE,
|
||||
};
|
||||
|
||||
var RCTMap = requireNativeComponent('RCTMap', MapView, {
|
||||
const RCTMap = requireNativeComponent('RCTMap', MapView, {
|
||||
nativeOnly: {onChange: true, onPress: true}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
+ (RCTMapAnnotation *)RCTMapAnnotation:(id)json;
|
||||
+ (RCTMapOverlay *)RCTMapOverlay:(id)json;
|
||||
|
||||
typedef NSArray RCTMapAnnotationArray;
|
||||
+ (NSArray<RCTMapAnnotation *> *)RCTMapAnnotationArray:(id)json;
|
||||
|
||||
typedef NSArray RCTMapOverlayArray;
|
||||
+ (NSArray<RCTMapOverlay *> *)RCTMapOverlayArray:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
@@ -50,9 +50,14 @@ RCT_ENUM_CONVERTER(MKMapType, (@{
|
||||
annotation.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]];
|
||||
annotation.tintColor = [RCTConvert UIColor:json[@"tintColor"]];
|
||||
annotation.image = [RCTConvert UIImage:json[@"image"]];
|
||||
if (annotation.tintColor && annotation.image) {
|
||||
annotation.image = [annotation.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
}
|
||||
annotation.viewIndex =
|
||||
[RCTConvert NSInteger:json[@"viewIndex"] ?: @(NSNotFound)];
|
||||
annotation.leftCalloutViewIndex =
|
||||
[RCTConvert NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)];
|
||||
annotation.rightCalloutViewIndex =
|
||||
[RCTConvert NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)];
|
||||
annotation.detailCalloutViewIndex =
|
||||
[RCTConvert NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)];
|
||||
return annotation;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
|
||||
|
||||
- (void)setAnnotations:(RCTMapAnnotationArray *)annotations;
|
||||
- (void)setOverlays:(RCTMapOverlayArray *)overlays;
|
||||
- (void)setAnnotations:(NSArray<RCTMapAnnotation *> *)annotations;
|
||||
- (void)setOverlays:(NSArray<RCTMapOverlay *> *)overlays;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,6 +23,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
{
|
||||
UIView *_legalLabel;
|
||||
CLLocationManager *_locationManager;
|
||||
NSMutableArray<UIView *> *_reactSubviews;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -30,6 +31,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
if ((self = [super init])) {
|
||||
|
||||
_hasStartedRendering = NO;
|
||||
_reactSubviews = [NSMutableArray new];
|
||||
|
||||
// Find Apple link label
|
||||
for (UIView *subview in self.subviews) {
|
||||
@@ -49,6 +51,21 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
[_regionChangeObserveTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[_reactSubviews insertObject:subview atIndex:atIndex];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[_reactSubviews removeObject:subview];
|
||||
}
|
||||
|
||||
- (NSArray<UIView *> *)reactSubviews
|
||||
{
|
||||
return _reactSubviews;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
@@ -111,7 +128,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
|
||||
// TODO: this doesn't preserve order. Should it? If so we should change the
|
||||
// algorithm. If not, it would be more efficient to use an NSSet
|
||||
- (void)setAnnotations:(RCTMapAnnotationArray *)annotations
|
||||
- (void)setAnnotations:(NSArray<RCTMapAnnotation *> *)annotations
|
||||
{
|
||||
NSMutableArray<NSString *> *newAnnotationIDs = [NSMutableArray new];
|
||||
NSMutableArray<RCTMapAnnotation *> *annotationsToDelete = [NSMutableArray new];
|
||||
@@ -154,7 +171,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
|
||||
// TODO: this doesn't preserve order. Should it? If so we should change the
|
||||
// algorithm. If not, it would be more efficient to use an NSSet
|
||||
- (void)setOverlays:(RCTMapOverlayArray *)overlays
|
||||
- (void)setOverlays:(NSArray<RCTMapOverlay *> *)overlays
|
||||
{
|
||||
NSMutableArray *newOverlayIDs = [NSMutableArray new];
|
||||
NSMutableArray *overlaysToDelete = [NSMutableArray new];
|
||||
|
||||
@@ -17,5 +17,9 @@
|
||||
@property (nonatomic, assign) BOOL animateDrop;
|
||||
@property (nonatomic, strong) UIColor *tintColor;
|
||||
@property (nonatomic, strong) UIImage *image;
|
||||
@property (nonatomic, assign) NSInteger viewIndex;
|
||||
@property (nonatomic, assign) NSInteger leftCalloutViewIndex;
|
||||
@property (nonatomic, assign) NSInteger rightCalloutViewIndex;
|
||||
@property (nonatomic, assign) NSInteger detailCalloutViewIndex;
|
||||
|
||||
@end
|
||||
|
||||
@@ -67,8 +67,8 @@ RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType)
|
||||
RCT_EXPORT_VIEW_PROPERTY(annotations, RCTMapAnnotationArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(overlays, RCTMapOverlayArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(annotations, NSArray<RCTMapAnnotation *>)
|
||||
RCT_EXPORT_VIEW_PROPERTY(overlays, NSArray<RCTMapOverlay *>)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||
@@ -125,77 +125,124 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||
}
|
||||
}
|
||||
|
||||
- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTMapAnnotation *)annotation
|
||||
- (MKAnnotationView *)mapView:(RCTMap *)mapView
|
||||
viewForAnnotation:(RCTMapAnnotation *)annotation
|
||||
{
|
||||
if (![annotation isKindOfClass:[RCTMapAnnotation class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
MKAnnotationView *annotationView;
|
||||
if (annotation.image) {
|
||||
if (annotation.tintColor) {
|
||||
annotationView.clipsToBounds = YES;
|
||||
if (annotation.viewIndex != NSNotFound) {
|
||||
|
||||
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;
|
||||
NSString *const reuseIdentifier = @"RCTCustomViewAnnotation";
|
||||
annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier];
|
||||
if (!annotationView) {
|
||||
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
|
||||
reuseIdentifier:reuseIdentifier];
|
||||
}
|
||||
|
||||
for (UIView *view in annotationView.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
UIView *reactView = mapView.reactSubviews[annotation.viewIndex];
|
||||
annotationView.bounds = reactView.frame;
|
||||
[annotationView addSubview:reactView];
|
||||
|
||||
} else if (annotation.image) {
|
||||
|
||||
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];
|
||||
annotationView =
|
||||
[mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?:
|
||||
[[MKPinAnnotationView alloc] initWithAnnotation:annotation
|
||||
reuseIdentifier:reuseIdentifier];
|
||||
((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop;
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
|
||||
|
||||
if (![annotationView respondsToSelector:@selector(pinTintColor)]) {
|
||||
NSString *hexColor = annotation.tintColor ? RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed;
|
||||
((MKPinAnnotationView *)annotationView).pinColor = [RCTConvert MKPinAnnotationColor:hexColor];
|
||||
NSString *hexColor = annotation.tintColor ?
|
||||
RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed;
|
||||
((MKPinAnnotationView *)annotationView).pinColor =
|
||||
[RCTConvert MKPinAnnotationColor:hexColor];
|
||||
} else
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor];
|
||||
((MKPinAnnotationView *)annotationView).pinTintColor =
|
||||
annotation.tintColor ?: [MKPinAnnotationView redPinColor];
|
||||
}
|
||||
}
|
||||
annotationView.canShowCallout = true;
|
||||
|
||||
annotationView.leftCalloutAccessoryView = nil;
|
||||
if (annotation.hasLeftCallout) {
|
||||
annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
|
||||
if (annotation.leftCalloutViewIndex != NSNotFound) {
|
||||
annotationView.leftCalloutAccessoryView =
|
||||
mapView.reactSubviews[annotation.leftCalloutViewIndex];
|
||||
} else if (annotation.hasLeftCallout) {
|
||||
annotationView.leftCalloutAccessoryView =
|
||||
[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
|
||||
} else {
|
||||
annotationView.leftCalloutAccessoryView = nil;
|
||||
}
|
||||
|
||||
annotationView.rightCalloutAccessoryView = nil;
|
||||
if (annotation.hasRightCallout) {
|
||||
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
|
||||
if (annotation.rightCalloutViewIndex != NSNotFound) {
|
||||
annotationView.rightCalloutAccessoryView =
|
||||
mapView.reactSubviews[annotation.rightCalloutViewIndex];
|
||||
} else if (annotation.hasRightCallout) {
|
||||
annotationView.rightCalloutAccessoryView =
|
||||
[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
|
||||
} else {
|
||||
annotationView.rightCalloutAccessoryView = nil;
|
||||
}
|
||||
|
||||
//http://stackoverflow.com/questions/32581049/mapkit-ios-9-detailcalloutaccessoryview-usage
|
||||
if ([annotationView respondsToSelector:@selector(detailCalloutAccessoryView)]) {
|
||||
if (annotation.detailCalloutViewIndex != NSNotFound) {
|
||||
UIView *calloutView = mapView.reactSubviews[annotation.detailCalloutViewIndex];
|
||||
NSLayoutConstraint *widthConstraint =
|
||||
[NSLayoutConstraint constraintWithItem:calloutView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:calloutView.frame.size.width];
|
||||
[calloutView addConstraint:widthConstraint];
|
||||
NSLayoutConstraint *heightConstraint =
|
||||
[NSLayoutConstraint constraintWithItem:calloutView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:calloutView.frame.size.height];
|
||||
[calloutView addConstraint:heightConstraint];
|
||||
annotationView.detailCalloutAccessoryView = calloutView;
|
||||
} else {
|
||||
annotationView.detailCalloutAccessoryView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
return annotationView;
|
||||
}
|
||||
|
||||
- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView rendererForOverlay:(RCTMapOverlay *)overlay
|
||||
- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView
|
||||
rendererForOverlay:(RCTMapOverlay *)overlay
|
||||
{
|
||||
if ([overlay isKindOfClass:[RCTMapOverlay class]]) {
|
||||
MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
|
||||
MKPolylineRenderer *polylineRenderer =
|
||||
[[MKPolylineRenderer alloc] initWithPolyline:overlay];
|
||||
polylineRenderer.strokeColor = overlay.strokeColor;
|
||||
polylineRenderer.lineWidth = overlay.lineWidth;
|
||||
return polylineRenderer;
|
||||
@@ -204,11 +251,12 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
|
||||
- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view
|
||||
calloutAccessoryControlTapped:(UIControl *)control
|
||||
{
|
||||
if (mapView.onPress) {
|
||||
|
||||
// Pass to js
|
||||
// Pass to JS
|
||||
RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
|
||||
mapView.onPress(@{
|
||||
@"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right",
|
||||
@@ -236,13 +284,15 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||
{
|
||||
[self _regionChanged:mapView];
|
||||
|
||||
mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
|
||||
target:self
|
||||
selector:@selector(_onTick:)
|
||||
userInfo:@{ RCTMapViewKey: mapView }
|
||||
repeats:YES];
|
||||
mapView.regionChangeObserveTimer =
|
||||
[NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
|
||||
target:self
|
||||
selector:@selector(_onTick:)
|
||||
userInfo:@{ RCTMapViewKey: mapView }
|
||||
repeats:YES];
|
||||
|
||||
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
|
||||
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer
|
||||
forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(__unused BOOL)animated
|
||||
@@ -288,15 +338,18 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
|
||||
// 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;
|
||||
newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
|
||||
} else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) {
|
||||
} else if (mapView.minDelta > FLT_EPSILON &&
|
||||
region.span.longitudeDelta < mapView.minDelta) {
|
||||
needZoom = YES;
|
||||
newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer);
|
||||
}
|
||||
if (needZoom) {
|
||||
region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
|
||||
region.span.latitudeDelta =
|
||||
region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
|
||||
region.span.longitudeDelta = newLongitudeDelta;
|
||||
mapView.region = region;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user