[ReactNative] Oss GeoMap
This commit is contained in:
parent
78ec0df464
commit
fa87d83af5
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule MapViewExample
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var {
|
||||
MapView,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var MapRegionInput = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
region: React.PropTypes.shape({
|
||||
latitude: React.PropTypes.number,
|
||||
longitude: React.PropTypes.number,
|
||||
latitudeDelta: React.PropTypes.number,
|
||||
longitudeDelta: React.PropTypes.number,
|
||||
}),
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
latitudeDelta: 0,
|
||||
longitudeDelta: 0,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
this.setState(nextProps.region);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var region = this.state;
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.row}>
|
||||
<Text>
|
||||
{'Latitude'}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={'' + region.latitude}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLatitude}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text>
|
||||
{'Longitude'}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={'' + region.longitude}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLongitude}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text>
|
||||
{'Latitude delta'}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={'' + region.latitudeDelta}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLatitudeDelta}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text>
|
||||
{'Longitude delta'}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={'' + region.longitudeDelta}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLongitudeDelta}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.changeButton}>
|
||||
<Text onPress={this._change}>
|
||||
{'Change'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_onChangeLatitude: function(e) {
|
||||
this.setState({latitude: parseFloat(e.nativeEvent.text)});
|
||||
},
|
||||
|
||||
_onChangeLongitude: function(e) {
|
||||
this.setState({longitude: parseFloat(e.nativeEvent.text)});
|
||||
},
|
||||
|
||||
_onChangeLatitudeDelta: function(e) {
|
||||
this.setState({latitudeDelta: parseFloat(e.nativeEvent.text)});
|
||||
},
|
||||
|
||||
_onChangeLongitudeDelta: function(e) {
|
||||
this.setState({longitudeDelta: parseFloat(e.nativeEvent.text)});
|
||||
},
|
||||
|
||||
_change: function() {
|
||||
this.props.onChange(this.state);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var MapViewExample = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
mapRegion: null,
|
||||
mapRegionInput: null,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<MapView
|
||||
style={styles.map}
|
||||
onRegionChange={this._onRegionChanged}
|
||||
region={this.state.mapRegion}
|
||||
/>
|
||||
<MapRegionInput
|
||||
onChange={this._onRegionInputChanged}
|
||||
region={this.state.mapRegionInput}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_onRegionChanged(region) {
|
||||
this.setState({mapRegionInput: region});
|
||||
},
|
||||
|
||||
_onRegionInputChanged(region) {
|
||||
this.setState({
|
||||
mapRegion: region,
|
||||
mapRegionInput: region,
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
map: {
|
||||
height: 150,
|
||||
margin: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: '#000000',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
textInput: {
|
||||
width: 150,
|
||||
height: 20,
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#aaaaaa',
|
||||
fontSize: 13,
|
||||
padding: 4,
|
||||
},
|
||||
changeButton: {
|
||||
alignSelf: 'center',
|
||||
marginTop: 5,
|
||||
padding: 3,
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#777777',
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<MapView>';
|
||||
exports.description = 'Base component to display maps';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Map',
|
||||
render() { return <MapViewExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Map shows user location',
|
||||
render() {
|
||||
return <MapView style={styles.map} showsUserLocation={true} />;
|
||||
}
|
||||
}
|
||||
];
|
|
@ -37,6 +37,7 @@ var EXAMPLES = [
|
|||
require('./SwitchExample'),
|
||||
require('./SliderExample'),
|
||||
require('./CameraRollExample.ios'),
|
||||
require('./MapViewExample'),
|
||||
];
|
||||
|
||||
var UIExplorerList = React.createClass({
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule MapView
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var deepDiffer = require('deepDiffer');
|
||||
var insetsDiffer = require('insetsDiffer');
|
||||
var merge = require('merge');
|
||||
|
||||
var MapView = React.createClass({
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* Used to style and layout the `MapView`. See `StyleSheet.js` and
|
||||
* `ViewStylePropTypes.js` for more info.
|
||||
*/
|
||||
style: View.propTypes.style,
|
||||
|
||||
/**
|
||||
* If `true` the app will ask for the user's location and focus on it.
|
||||
* Default value is `false`.
|
||||
*
|
||||
* **NOTE**: You need to add NSLocationWhenInUseUsageDescription key in
|
||||
* Info.plist to enable geolocation, otherwise it is going
|
||||
* to *fail silently*!
|
||||
*/
|
||||
showsUserLocation: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* If `false` the user won't be able to pinch/zoom the map.
|
||||
* Default `value` is true.
|
||||
*/
|
||||
zoomEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* When this property is set to `true` and a valid camera is associated with
|
||||
* the map, the camera’s heading angle is used to rotate the plane of the
|
||||
* map around its center point. When this property is set to `false`, the
|
||||
* camera’s heading angle is ignored and the map is always oriented so
|
||||
* that true north is situated at the top of the map view
|
||||
*/
|
||||
rotateEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* When this property is set to `true` and a valid camera is associated
|
||||
* with the map, the camera’s pitch angle is used to tilt the plane
|
||||
* of the map. When this property is set to `false`, the camera’s pitch
|
||||
* angle is ignored and the map is always displayed as if the user
|
||||
* is looking straight down onto it.
|
||||
*/
|
||||
pitchEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* If `false` the user won't be able to change the map region being displayed.
|
||||
* Default value is `true`.
|
||||
*/
|
||||
scrollEnabled: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The region to be displayed by the map.
|
||||
*
|
||||
* The region is defined by the center coordinates and the span of
|
||||
* coordinates to display.
|
||||
*/
|
||||
region: React.PropTypes.shape({
|
||||
/**
|
||||
* Coordinates for the center of the map.
|
||||
*/
|
||||
latitude: React.PropTypes.number.isRequired,
|
||||
longitude: React.PropTypes.number.isRequired,
|
||||
|
||||
/**
|
||||
* Distance between the minimun and the maximum latitude/longitude
|
||||
* to be displayed.
|
||||
*/
|
||||
latitudeDelta: React.PropTypes.number.isRequired,
|
||||
longitudeDelta: React.PropTypes.number.isRequired,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Maximum size of area that can be displayed.
|
||||
*/
|
||||
maxDelta: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* Minimum size of area that can be displayed.
|
||||
*/
|
||||
minDelta: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* Insets for the map's legal label, originally at bottom left of the map.
|
||||
* See `EdgeInsetsPropType.js` for more information.
|
||||
*/
|
||||
legalLabelInsets: EdgeInsetsPropType,
|
||||
|
||||
/**
|
||||
* Callback that is called continuously when the user is dragging the map.
|
||||
*/
|
||||
onRegionChange: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Callback that is called once, when the user is done moving the map.
|
||||
*/
|
||||
onRegionChangeComplete: React.PropTypes.func,
|
||||
},
|
||||
|
||||
_onChange: function(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 (
|
||||
<RKMap
|
||||
style={this.props.style}
|
||||
showsUserLocation={this.props.showsUserLocation}
|
||||
zoomEnabled={this.props.zoomEnabled}
|
||||
rotateEnabled={this.props.rotateEnabled}
|
||||
pitchEnabled={this.props.pitchEnabled}
|
||||
scrollEnabled={this.props.scrollEnabled}
|
||||
region={this.props.region}
|
||||
maxDelta={this.props.maxDelta}
|
||||
minDelta={this.props.minDelta}
|
||||
legalLabelInsets={this.props.legalLabelInsets}
|
||||
onChange={this._onChange}
|
||||
onTouchStart={this.props.onTouchStart}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var RKMap = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(
|
||||
ReactIOSViewAttributes.UIView, {
|
||||
showsUserLocation: true,
|
||||
zoomEnabled: true,
|
||||
rotateEnabled: true,
|
||||
pitchEnabled: true,
|
||||
scrollEnabled: true,
|
||||
region: {diff: deepDiffer},
|
||||
maxDelta: true,
|
||||
minDelta: true,
|
||||
legalLabelInsets: {diff: insetsDiffer},
|
||||
}
|
||||
),
|
||||
uiViewClassName: 'RCTMap',
|
||||
});
|
||||
|
||||
module.exports = MapView;
|
|
@ -11,6 +11,7 @@ var ReactNative = {
|
|||
CameraRoll: require('CameraRoll'),
|
||||
DatePickerIOS: require('DatePickerIOS'),
|
||||
ExpandingText: require('ExpandingText'),
|
||||
MapView: require('MapView'),
|
||||
Image: require('Image'),
|
||||
LayoutAnimation: require('LayoutAnimation'),
|
||||
ListView: require('ListView'),
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
|
||||
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
|
||||
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
|
||||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
|
||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
|
||||
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
|
||||
|
@ -134,6 +136,10 @@
|
|||
14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = "<group>"; };
|
||||
14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = "<group>"; };
|
||||
14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = "<group>"; };
|
||||
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
|
||||
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
|
||||
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
|
||||
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
|
||||
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
|
||||
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
|
||||
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
|
||||
|
@ -219,6 +225,10 @@
|
|||
14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */,
|
||||
14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */,
|
||||
14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */,
|
||||
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
|
||||
14435CE21AAC4AE100FC20F4 /* RCTMap.m */,
|
||||
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */,
|
||||
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */,
|
||||
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */,
|
||||
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
|
||||
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
|
||||
|
@ -435,12 +445,14 @@
|
|||
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */,
|
||||
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
|
||||
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
|
||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
|
||||
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
|
||||
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
|
||||
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
|
||||
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */,
|
||||
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
|
||||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
|
||||
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
|
||||
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
|
||||
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <MapKit/MapKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern const CLLocationDegrees RCTMapDefaultSpan;
|
||||
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
|
||||
extern const CGFloat RCTMapZoomBoundBuffer;
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
|
||||
@interface RCTMap: MKMapView
|
||||
|
||||
@property (nonatomic, assign) BOOL followUserLocation;
|
||||
@property (nonatomic, copy) NSDictionary *JSONRegion;
|
||||
@property (nonatomic, assign) CGFloat minDelta;
|
||||
@property (nonatomic, assign) CGFloat maxDelta;
|
||||
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
|
||||
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
|
||||
|
||||
@end
|
||||
|
||||
#define FLUSH_NAN(value) \
|
||||
(isnan(value) ? 0 : value)
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTMap.h"
|
||||
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
const CLLocationDegrees RCTMapDefaultSpan = 0.005;
|
||||
const NSTimeInterval RCTMapRegionChangeObserveInterval = 0.1;
|
||||
const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
|
||||
@interface RCTMap()
|
||||
|
||||
@property (nonatomic, strong) UIView *legalLabel;
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTMap
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Find Apple link label
|
||||
for (UIView *subview in self.subviews) {
|
||||
if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) {
|
||||
// This check is super hacky, but the whole premise of moving around Apple's internal subviews is super hacky
|
||||
_legalLabel = subview;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.regionChangeObserveTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
// Force resize subviews - only the layer is resized by default
|
||||
CGRect mapFrame = self.frame;
|
||||
self.frame = CGRectZero;
|
||||
self.frame = mapFrame;
|
||||
|
||||
if (_legalLabel) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CGRect frame = _legalLabel.frame;
|
||||
if (_legalLabelInsets.left) {
|
||||
frame.origin.x = _legalLabelInsets.left;
|
||||
} else if (_legalLabelInsets.right) {
|
||||
frame.origin.x = mapFrame.size.width - _legalLabelInsets.right - frame.size.width;
|
||||
}
|
||||
if (_legalLabelInsets.top) {
|
||||
frame.origin.y = _legalLabelInsets.top;
|
||||
} else if (_legalLabelInsets.bottom) {
|
||||
frame.origin.y = mapFrame.size.height - _legalLabelInsets.bottom - frame.size.height;
|
||||
}
|
||||
_legalLabel.frame = frame;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Accessors
|
||||
|
||||
- (void)setShowsUserLocation:(BOOL)showsUserLocation
|
||||
{
|
||||
if (self.showsUserLocation != showsUserLocation) {
|
||||
if (showsUserLocation && !_locationManager) {
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
}
|
||||
[super setShowsUserLocation:showsUserLocation];
|
||||
|
||||
// If it needs to show user location, force map view centered
|
||||
// on user's current location on user location updates
|
||||
self.followUserLocation = showsUserLocation;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setJSONRegion:(NSDictionary *)region
|
||||
{
|
||||
if (region) {
|
||||
MKCoordinateRegion coordinateRegion = self.region;
|
||||
if ([region[@"latitude"] isKindOfClass:[NSNumber class]]) {
|
||||
coordinateRegion.center.latitude = [region[@"latitude"] doubleValue];
|
||||
} else {
|
||||
RCTLogError(@"region must include numeric latitude, got: %@", region);
|
||||
return;
|
||||
}
|
||||
if ([region[@"longitude"] isKindOfClass:[NSNumber class]]) {
|
||||
coordinateRegion.center.longitude = [region[@"longitude"] doubleValue];
|
||||
} else {
|
||||
RCTLogError(@"region must include numeric longitude, got: %@", region);
|
||||
return;
|
||||
}
|
||||
if ([region[@"latitudeDelta"] isKindOfClass:[NSNumber class]]) {
|
||||
coordinateRegion.span.latitudeDelta = [region[@"latitudeDelta"] doubleValue];
|
||||
}
|
||||
if ([region[@"longitudeDelta"] isKindOfClass:[NSNumber class]]) {
|
||||
coordinateRegion.span.longitudeDelta = [region[@"longitudeDelta"] doubleValue];
|
||||
}
|
||||
|
||||
[self setRegion:coordinateRegion animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)JSONRegion
|
||||
{
|
||||
MKCoordinateRegion region = self.region;
|
||||
if (!CLLocationCoordinate2DIsValid(region.center)) {
|
||||
return nil;
|
||||
}
|
||||
return @{
|
||||
@"latitude": @(FLUSH_NAN(region.center.latitude)),
|
||||
@"longitude": @(FLUSH_NAN(region.center.longitude)),
|
||||
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
|
||||
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTMapManager : RCTViewManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTMapManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTMap.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
|
||||
@interface RCTMapManager() <MKMapViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTMapManager
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
RCTMap *map = [[RCTMap alloc] init];
|
||||
map.delegate = self;
|
||||
return map;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation);
|
||||
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled);
|
||||
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled);
|
||||
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled);
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled);
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxDelta);
|
||||
RCT_EXPORT_VIEW_PROPERTY(minDelta);
|
||||
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets);
|
||||
RCT_REMAP_VIEW_PROPERTY(region, JSONRegion)
|
||||
|
||||
#pragma mark MKMapViewDelegate
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location
|
||||
{
|
||||
if (mapView.followUserLocation) {
|
||||
MKCoordinateRegion region;
|
||||
region.span.latitudeDelta = RCTMapDefaultSpan;
|
||||
region.span.longitudeDelta = RCTMapDefaultSpan;
|
||||
region.center = location.coordinate;
|
||||
[mapView setRegion:region animated:YES];
|
||||
|
||||
// Move to user location only for the first time it loads up.
|
||||
mapView.followUserLocation = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(BOOL)animated
|
||||
{
|
||||
[self _regionChanged:mapView];
|
||||
|
||||
mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
|
||||
target:self
|
||||
selector:@selector(_onTick:)
|
||||
userInfo:@{ @"mapView": mapView }
|
||||
repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated
|
||||
{
|
||||
[self _regionChanged:mapView];
|
||||
[self _emitRegionChangeEvent:mapView continuous:NO];
|
||||
|
||||
[mapView.regionChangeObserveTimer invalidate];
|
||||
mapView.regionChangeObserveTimer = nil;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)_onTick:(NSTimer *)timer
|
||||
{
|
||||
[self _regionChanged:timer.userInfo[@"mapView"]];
|
||||
}
|
||||
|
||||
- (void)_regionChanged:(RCTMap *)mapView
|
||||
{
|
||||
BOOL needZoom = NO;
|
||||
CGFloat newLongitudeDelta = 0.0f;
|
||||
MKCoordinateRegion region = mapView.region;
|
||||
// On iOS 7, it's possible that we observe invalid locations during initialization of the map.
|
||||
// Filter those out.
|
||||
if (!CLLocationCoordinate2DIsValid(region.center)) {
|
||||
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.
|
||||
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) {
|
||||
needZoom = YES;
|
||||
newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer);
|
||||
}
|
||||
if (needZoom) {
|
||||
region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
|
||||
region.span.longitudeDelta = newLongitudeDelta;
|
||||
mapView.region = region;
|
||||
}
|
||||
|
||||
// Continously observe region changes
|
||||
[self _emitRegionChangeEvent:mapView continuous:YES];
|
||||
}
|
||||
|
||||
- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
|
||||
{
|
||||
NSDictionary *region = mapView.JSONRegion;
|
||||
if (region) {
|
||||
NSDictionary *event = @{
|
||||
@"target": [mapView reactTag],
|
||||
@"continuous": @(continuous),
|
||||
@"region": mapView.JSONRegion,
|
||||
};
|
||||
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in New Issue