diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b2027bc2..f9ae8057e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## 0.16.2 (August 17, 2017) +* Android: [#1563](https://github.com/airbnb/react-native-maps/pull/#1563) Add missing native method for setting initial region +* iOS: [#1187](https://github.com/airbnb/react-native-maps/pull/1187) Reverted due to build issues + +## 0.16.1 (August 15, 2017) +* Android: [#1428](https://github.com/airbnb/react-native-maps/pull/#1428) Add ability to load marker image from drawable +* iOS: [#1187](https://github.com/airbnb/react-native-maps/pull/1187) Improve marker performance +* iOS/Android: [#1458](https://github.com/airbnb/react-native-maps/pull/1458) Add Google Maps legalNotice constant +* JS: [#1546](https://github.com/airbnb/react-native-maps/pull/1546) Fix initial region native prop + +## 0.16.0 (August 9, 2017) +* Android: [#1481](https://github.com/airbnb/react-native-maps/pull/1481) Handle Android RN 0.47 breaking change +* iOS: [#1357](https://github.com/airbnb/react-native-maps/pull/1357) add MKTileOverlayRenderer +* iOS: [#1369](https://github.com/airbnb/react-native-maps/pull/1369) Add onMapReady callback +* Android/iOS/JS: [#1360](https://github.com/airbnb/react-native-maps/pull/1360) Add minZoom and maxZoom properties for android and ios +* JS: [#1479](https://github.com/airbnb/react-native-maps/pull/1479) Fix timing function used in AnimatedRegion.spring + ## 0.15.3 (June 27, 2017) * iOS: [#1362](https://github.com/airbnb/react-native-maps/pull/1362) Updates for React 0.43-0.45 and React 16. diff --git a/docs/mapview.md b/docs/mapview.md index 3df5640c3b..a73e123b8c 100644 --- a/docs/mapview.md +++ b/docs/mapview.md @@ -22,6 +22,8 @@ | `showsIndoors` | `Boolean` | `true` | A Boolean indicating whether indoor maps should be enabled. | `showsIndoorLevelPicker` | `Boolean` | `false` | A Boolean indicating whether indoor level picker should be enabled. **Note:** Android only. | `zoomEnabled` | `Boolean` | `true` | If `false` the user won't be able to pinch/zoom the map. +| `minZoomLevel` | `Number` | `0` | Minimum zoom value for the map, must be between 0 and 20 +| `maxZoomLevel` | `Number` | `20` | Maximum zoom value for the map, must be between 0 and 20 | `rotateEnabled` | `Boolean` | `true` | If `false` the user won't be able to pinch/rotate the map. | `scrollEnabled` | `Boolean` | `true` | If `false` the user won't be able to change the map region being displayed. | `pitchEnabled` | `Boolean` | `true` | If `false` the user won't be able to adjust the camera’s pitch angle. @@ -40,6 +42,7 @@ To access event data, you will need to use `e.nativeEvent`. For example, `onPres | Event Name | Returns | Notes |---|---|---| +| `onMapReady` | | Callback that is called once the map is fully loaded. | `onRegionChange` | `Region` | Callback that is called continuously when the region changes, such as when a user is dragging the map. | `onRegionChangeComplete` | `Region` | Callback that is called once when the region changes, such as when the user is done moving the map. | `onPress` | `{ coordinate: LatLng, position: Point }` | Callback that is called when user taps on the map. diff --git a/example/ios/Podfile b/example/ios/Podfile index 656f283e1f..fd01b85a01 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -5,9 +5,10 @@ platform :ios, '8.0' # Change 'AirMapsExplorer' to match the target in your Xcode project. target 'AirMapsExplorer' do + rn_path = '../../node_modules/react-native' - pod 'Yoga', :path => '../../node_modules/react-native/ReactCommon/yoga/Yoga.podspec' - pod 'React', path: '../../node_modules/react-native', :subspecs => [ + pod 'Yoga', path: "#{rn_path}/ReactCommon/yoga/Yoga.podspec" + pod 'React', path: rn_path, subspecs: [ 'Core', 'RCTActionSheet', 'RCTAnimation', @@ -22,19 +23,21 @@ target 'AirMapsExplorer' do 'BatchedBridge' ] - pod 'GoogleMaps' # <~~ remove this line if you do not want to support GoogleMaps on iOS + pod 'GoogleMaps' # Remove this line if you don't want to support GoogleMaps on iOS pod 'react-native-maps', path: '../../' - pod 'react-native-google-maps', path: '../../' # <~~ if you need GoogleMaps support on iOS - + pod 'react-native-google-maps', path: '../../' # If you need GoogleMaps support on iOS end - post_install do |installer| installer.pods_project.targets.each do |target| - if target.name == "react-native-google-maps" + if target.name == 'react-native-google-maps' target.build_configurations.each do |config| config.build_settings['CLANG_ENABLE_MODULES'] = 'No' end end + + if target.name == "React" + target.remove_from_project + end end end diff --git a/lib/android/gradle.properties b/lib/android/gradle.properties index e17f7611fb..5de65696c7 100644 --- a/lib/android/gradle.properties +++ b/lib/android/gradle.properties @@ -1,5 +1,5 @@ VERSION_CODE=4 -VERSION_NAME=0.15.3 +VERSION_NAME=0.16.2 GROUP=com.airbnb.android POM_DESCRIPTION=React Native Map view component for Android diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java index e8f98a7663..9ba6295cd1 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java @@ -75,6 +75,11 @@ public void setRegion(AirMapView view, ReadableMap region) { view.setRegion(region); } + @ReactProp(name = "initialRegion") + public void setInitialRegion(AirMapView view, ReadableMap initialRegion) { + view.setInitialRegion(initialRegion); + } + @ReactProp(name = "mapType") public void setMapType(AirMapView view, @Nullable String mapType) { int typeId = MAP_TYPES.get(mapType); diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapModule.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapModule.java index 1a096255e6..bd1fb6ed0f 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapModule.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapModule.java @@ -15,6 +15,7 @@ import com.facebook.react.uimanager.UIBlock; import com.facebook.react.uimanager.UIManagerModule; import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.common.GoogleApiAvailability; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -22,6 +23,9 @@ import java.io.FileOutputStream; import java.io.IOException; +import java.util.Map; +import java.util.HashMap; + import javax.annotation.Nullable; public class AirMapModule extends ReactContextBaseJavaModule { @@ -40,6 +44,13 @@ public String getName() { return "AirMapModule"; } + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("legalNotice", GoogleApiAvailability.getInstance().getOpenSourceSoftwareLicenseInfo(getReactApplicationContext())); + return constants; + } + public Activity getActivity() { return getCurrentActivity(); } diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java index 551296f96d..31c0409dde 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java @@ -71,6 +71,7 @@ public class AirMapView extends MapView implements GoogleMap.InfoWindowAdapter, private boolean handlePanDrag = false; private boolean moveOnMarkerPress = true; private boolean cacheEnabled = false; + private boolean initialRegionSet = false; private static final String[] PERMISSIONS = new String[]{ "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"}; @@ -305,7 +306,9 @@ public void onHostResume() { map.setMyLocationEnabled(showUserLocation); } synchronized (AirMapView.this) { - AirMapView.this.onResume(); + if (!destroyed) { + AirMapView.this.onResume(); + } paused = false; } } @@ -359,6 +362,13 @@ public synchronized void doDestroy() { onDestroy(); } + public void setInitialRegion(ReadableMap initialRegion) { + if (!initialRegionSet && initialRegion != null) { + setRegion(initialRegion); + initialRegionSet = true; + } + } + public void setRegion(ReadableMap region) { if (region == null) return; @@ -713,13 +723,13 @@ public boolean dispatchTouchEvent(MotionEvent ev) { // Timer Implementation public void startMonitoringRegion() { - if (isMonitoringRegion) return; + if (map == null || isMonitoringRegion) return; timerHandler.postDelayed(timerRunnable, 100); isMonitoringRegion = true; } public void stopMonitoringRegion() { - if (!isMonitoringRegion) return; + if (map == null || !isMonitoringRegion) return; timerHandler.removeCallbacks(timerRunnable); isMonitoringRegion = false; } @@ -732,16 +742,18 @@ public void stopMonitoringRegion() { @Override public void run() { - Projection projection = map.getProjection(); - VisibleRegion region = (projection != null) ? projection.getVisibleRegion() : null; - LatLngBounds bounds = (region != null) ? region.latLngBounds : null; - - if ((bounds != null) && - (lastBoundsEmitted == null || - LatLngBoundsUtils.BoundsAreDifferent(bounds, lastBoundsEmitted))) { - LatLng center = map.getCameraPosition().target; - lastBoundsEmitted = bounds; - eventDispatcher.dispatchEvent(new RegionChangeEvent(getId(), bounds, center, true)); + if (map != null) { + Projection projection = map.getProjection(); + VisibleRegion region = (projection != null) ? projection.getVisibleRegion() : null; + LatLngBounds bounds = (region != null) ? region.latLngBounds : null; + + if ((bounds != null) && + (lastBoundsEmitted == null || + LatLngBoundsUtils.BoundsAreDifferent(bounds, lastBoundsEmitted))) { + LatLng center = map.getCameraPosition().target; + lastBoundsEmitted = bounds; + eventDispatcher.dispatchEvent(new RegionChangeEvent(getId(), bounds, center, true)); + } } timerHandler.postDelayed(this, 100); diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java b/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java index 1347aa284a..4df0a6cc9a 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java @@ -24,7 +24,7 @@ public List createNativeModules(ReactApplicationContext reactConte return Arrays.asList(new AirMapModule(reactContext)); } - @Override + // Deprecated RN 0.47 public List> createJSModules() { return Collections.emptyList(); } diff --git a/lib/components/AnimatedRegion.js b/lib/components/AnimatedRegion.js index 29356ca9e3..ad18c5e077 100644 --- a/lib/components/AnimatedRegion.js +++ b/lib/components/AnimatedRegion.js @@ -111,25 +111,25 @@ export default class AnimatedMapRegion extends AnimatedWithChildren { spring(config) { var animations = []; config.hasOwnProperty('latitude') && - animations.push(Animated.timing(this.latitude, { + animations.push(Animated.spring(this.latitude, { ...config, toValue: config.latitude })); config.hasOwnProperty('longitude') && - animations.push(Animated.timing(this.longitude, { + animations.push(Animated.spring(this.longitude, { ...config, toValue: config.longitude })); config.hasOwnProperty('latitudeDelta') && - animations.push(Animated.timing(this.latitudeDelta, { + animations.push(Animated.spring(this.latitudeDelta, { ...config, toValue: config.latitudeDelta })); config.hasOwnProperty('longitudeDelta') && - animations.push(Animated.timing(this.longitudeDelta, { + animations.push(Animated.spring(this.longitudeDelta, { ...config, toValue: config.longitudeDelta })); diff --git a/lib/components/MapMarker.js b/lib/components/MapMarker.js index 6e2b8585dd..b81bab87af 100644 --- a/lib/components/MapMarker.js +++ b/lib/components/MapMarker.js @@ -257,7 +257,7 @@ class MapMarker extends React.Component { let image; if (this.props.image) { image = resolveAssetSource(this.props.image) || {}; - image = image.uri; + image = image.uri || this.props.image; } const AIRMapMarker = this.getAirComponent(); diff --git a/lib/components/MapView.js b/lib/components/MapView.js index c758979d0f..b3e2e5335f 100644 --- a/lib/components/MapView.js +++ b/lib/components/MapView.js @@ -321,6 +321,11 @@ const propTypes = { */ legalLabelInsets: EdgeInsetsPropType, + /** + * Callback that is called once the map is fully loaded. + */ + onMapReady: PropTypes.func, + /** * Callback that is called continuously when the user is dragging the map. */ @@ -444,24 +449,29 @@ class MapView extends React.Component { } _onMapReady() { - const { region, initialRegion } = this.props; + const { region, initialRegion, onMapReady } = this.props; if (region) { this.map.setNativeProps({ region }); } else if (initialRegion) { - this.map.setNativeProps({ region: initialRegion }); + this.map.setNativeProps({ initialRegion }); } this._updateStyle(); - this.setState({ isReady: true }); + this.setState({ isReady: true }, () => { + if (onMapReady) onMapReady(); + }); } _onLayout(e) { const { layout } = e.nativeEvent; if (!layout.width || !layout.height) return; if (this.state.isReady && !this.__layoutCalled) { - const region = this.props.region || this.props.initialRegion; + const { region, initialRegion } = this.props; if (region) { this.__layoutCalled = true; this.map.setNativeProps({ region }); + } else if (initialRegion) { + this.__layoutCalled = true; + this.map.setNativeProps({ initialRegion }); } } if (this.props.onLayout) { diff --git a/lib/ios/AirGoogleMaps/AIRGoogleMap.h b/lib/ios/AirGoogleMaps/AIRGoogleMap.h index 0e9554eb69..0d47b72b14 100644 --- a/lib/ios/AirGoogleMaps/AIRGoogleMap.h +++ b/lib/ios/AirGoogleMaps/AIRGoogleMap.h @@ -18,6 +18,7 @@ @property (nonatomic, assign) MKCoordinateRegion initialRegion; @property (nonatomic, assign) MKCoordinateRegion region; @property (nonatomic, assign) NSString *customMapStyleString; +@property (nonatomic, copy) RCTBubblingEventBlock onMapReady; @property (nonatomic, copy) RCTBubblingEventBlock onPress; @property (nonatomic, copy) RCTBubblingEventBlock onLongPress; @property (nonatomic, copy) RCTBubblingEventBlock onMarkerPress; @@ -40,6 +41,7 @@ @property (nonatomic, assign) BOOL showsUserLocation; @property (nonatomic, assign) BOOL showsMyLocationButton; +- (void)didFinishTileRendering; - (BOOL)didTapMarker:(GMSMarker *)marker; - (void)didTapPolyline:(GMSPolyline *)polyline; - (void)didTapPolygon:(GMSPolygon *)polygon; diff --git a/lib/ios/AirGoogleMaps/AIRGoogleMap.m b/lib/ios/AirGoogleMaps/AIRGoogleMap.m index f87d34c633..48b5ff4461 100644 --- a/lib/ios/AirGoogleMaps/AIRGoogleMap.m +++ b/lib/ios/AirGoogleMaps/AIRGoogleMap.m @@ -155,6 +155,10 @@ - (void)setRegion:(MKCoordinateRegion)region { self.camera = [AIRGoogleMap makeGMSCameraPositionFromMap:self andMKCoordinateRegion:region]; } +- (void)didFinishTileRendering { + if (self.onMapReady) self.onMapReady(@{}); +} + - (BOOL)didTapMarker:(GMSMarker *)marker { AIRGMSMarker *airMarker = (AIRGMSMarker *)marker; diff --git a/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m b/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m index c482305b87..086822b73a 100644 --- a/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m +++ b/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m @@ -59,6 +59,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL) RCT_EXPORT_VIEW_PROPERTY(showsMyLocationButton, BOOL) RCT_EXPORT_VIEW_PROPERTY(customMapStyleString, NSString) +RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) @@ -221,6 +222,14 @@ - (UIView *)view }]; } +- (NSDictionary *)constantsToExport { + return @{ @"legalNotice": [GMSServices openSourceLicenseInfo] }; +} + +- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView { + AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView; + [googleMapView didFinishTileRendering]; +} - (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView; diff --git a/lib/ios/AirMaps/AIRMap.h b/lib/ios/AirMaps/AIRMap.h index 4a88617e65..1d5f92cdcc 100644 --- a/lib/ios/AirMaps/AIRMap.h +++ b/lib/ios/AirMaps/AIRMap.h @@ -17,6 +17,7 @@ extern const CLLocationDegrees AIRMapDefaultSpan; extern const NSTimeInterval AIRMapRegionChangeObserveInterval; extern const CGFloat AIRMapZoomBoundBuffer; +extern const NSInteger AIRMapMaxZoomLevel; @interface AIRMap: MKMapView @@ -46,6 +47,7 @@ extern const CGFloat AIRMapZoomBoundBuffer; @property (nonatomic, assign) BOOL ignoreRegionChanges; +@property (nonatomic, copy) RCTBubblingEventBlock onMapReady; @property (nonatomic, copy) RCTBubblingEventBlock onChange; @property (nonatomic, copy) RCTBubblingEventBlock onPress; @property (nonatomic, copy) RCTBubblingEventBlock onPanDrag; diff --git a/lib/ios/AirMaps/AIRMap.m b/lib/ios/AirMaps/AIRMap.m index a9db64ab45..647be4b8a5 100644 --- a/lib/ios/AirMaps/AIRMap.m +++ b/lib/ios/AirMaps/AIRMap.m @@ -21,6 +21,7 @@ const CLLocationDegrees AIRMapDefaultSpan = 0.005; const NSTimeInterval AIRMapRegionChangeObserveInterval = 0.1; const CGFloat AIRMapZoomBoundBuffer = 0.01; +const NSInteger AIRMapMaxZoomLevel = 20; @interface MKMapView (UIGestureRecognizer) @@ -79,6 +80,9 @@ - (instancetype)init // be identical to the built-in callout view (which has a private API) self.calloutView = [SMCalloutView platformCalloutView]; self.calloutView.delegate = self; + + self.minZoomLevel = 0; + self.maxZoomLevel = AIRMapMaxZoomLevel; } return self; } diff --git a/lib/ios/AirMaps/AIRMapManager.h b/lib/ios/AirMaps/AIRMapManager.h index 29df98bfc8..1d73b405e7 100644 --- a/lib/ios/AirMaps/AIRMapManager.h +++ b/lib/ios/AirMaps/AIRMapManager.h @@ -12,7 +12,6 @@ #define MERCATOR_RADIUS 85445659.44705395 #define MERCATOR_OFFSET 268435456 -#define MAX_GOOGLE_LEVELS 20 @interface AIRMapManager : RCTViewManager diff --git a/lib/ios/AirMaps/AIRMapManager.m b/lib/ios/AirMaps/AIRMapManager.m index b1e4298be2..76aef88fe7 100644 --- a/lib/ios/AirMaps/AIRMapManager.m +++ b/lib/ios/AirMaps/AIRMapManager.m @@ -85,6 +85,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat) RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) +RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPanDrag, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) @@ -481,6 +482,8 @@ - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id mapView.maxZoomLevel) { + else if (zoomLevel > mapView.maxZoomLevel) { [self setCenterCoordinate:[mapView centerCoordinate] zoomLevel:mapView.maxZoomLevel animated:TRUE mapView:mapView]; } @@ -662,6 +665,8 @@ - (void)mapViewDidFinishRenderingMap:(AIRMap *)mapView fullyRendered:(BOOL)fully { [mapView finishLoading]; [mapView cacheViewIfNeeded]; + + mapView.onMapReady(@{}); } #pragma mark Private @@ -809,7 +814,7 @@ - (MKCoordinateSpan)coordinateSpanWithMapView:(AIRMap *)mapView double centerPixelY = [AIRMapManager latitudeToPixelSpaceY:centerCoordinate.latitude]; // determine the scale value from the zoom level - double zoomExponent = 20 - zoomLevel; + double zoomExponent = AIRMapMaxZoomLevel - zoomLevel; double zoomScale = pow(2, zoomExponent); // scale the map’s size in pixel space @@ -845,7 +850,7 @@ - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate mapView:(AIRMap *)mapView { // clamp large numbers to 28 - zoomLevel = MIN(zoomLevel, 28); + zoomLevel = MIN(zoomLevel, AIRMapMaxZoomLevel); // use the zoom level to compute the region MKCoordinateSpan span = [self coordinateSpanWithMapView:mapView centerCoordinate:centerCoordinate andZoomLevel:zoomLevel]; @@ -869,7 +874,7 @@ -(MKCoordinateRegion)coordinateRegionWithMapView:(AIRMap *)mapView double centerPixelY = [AIRMapManager latitudeToPixelSpaceY:centerCoordinate.latitude]; // determine the scale value from the zoom level - double zoomExponent = 20 - zoomLevel; + double zoomExponent = AIRMapMaxZoomLevel - zoomLevel; double zoomScale = pow(2, zoomExponent); // scale the map’s size in pixel space @@ -923,7 +928,7 @@ - (double) zoomLevel:(AIRMap *)mapView { CGSize mapSizeInPixels = mapView.bounds.size; double zoomScale = scaledMapWidth / mapSizeInPixels.width; double zoomExponent = log(zoomScale) / log(2); - double zoomLevel = 20 - zoomExponent; + double zoomLevel = AIRMapMaxZoomLevel - zoomExponent; return zoomLevel; } diff --git a/package.json b/package.json index 9ff2a7d832..cbd08cfe82 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "React Native Mapview component for iOS + Android", "main": "index.js", "author": "Leland Richardson ", - "version": "0.15.3", + "version": "0.16.2", "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start", "run:packager": "./node_modules/react-native/packager/packager.sh",