390 lines
15 KiB
Objective-C
390 lines
15 KiB
Objective-C
//
|
|
// AIRGoogleMapManager.m
|
|
// AirMaps
|
|
//
|
|
// Created by Gil Birman on 9/1/16.
|
|
//
|
|
|
|
|
|
#import "AIRGoogleMapManager.h"
|
|
#import <React/RCTViewManager.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTUIManager.h>
|
|
#import <React/RCTConvert+CoreLocation.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/RCTViewManager.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/UIView+React.h>
|
|
#import "RCTConvert+GMSMapViewType.h"
|
|
#import "AIRGoogleMap.h"
|
|
#import "AIRMapMarker.h"
|
|
#import "AIRMapPolyline.h"
|
|
#import "AIRMapPolygon.h"
|
|
#import "AIRMapCircle.h"
|
|
#import "SMCalloutView.h"
|
|
#import "AIRGoogleMapMarker.h"
|
|
#import "RCTConvert+AirMap.h"
|
|
|
|
#import <MapKit/MapKit.h>
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
static NSString *const RCTMapViewKey = @"MapView";
|
|
|
|
|
|
@interface AIRGoogleMapManager() <GMSMapViewDelegate>
|
|
{
|
|
BOOL didCallOnMapReady;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation AIRGoogleMapManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (UIView *)view
|
|
{
|
|
AIRGoogleMap *map = [AIRGoogleMap new];
|
|
map.delegate = self;
|
|
return map;
|
|
}
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(initialRegion, MKCoordinateRegion)
|
|
RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
|
|
RCT_EXPORT_VIEW_PROPERTY(showsBuildings, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL)
|
|
//RCT_EXPORT_VIEW_PROPERTY(showsScale, BOOL) // Not supported by GoogleMaps
|
|
RCT_EXPORT_VIEW_PROPERTY(showsTraffic, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(showsMyLocationButton, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(showsIndoorLevelPicker, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(customMapStyleString, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(mapPadding, UIEdgeInsets)
|
|
RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTBubblingEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onMarkerPress, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeComplete, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(mapType, GMSMapViewType)
|
|
RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
|
|
RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
|
|
|
|
RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag
|
|
withRegion:(MKCoordinateRegion)region
|
|
withDuration:(CGFloat)duration)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
// Core Animation must be used to control the animation's duration
|
|
// See http://stackoverflow.com/a/15663039/171744
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:duration/1000];
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
GMSCameraPosition *camera = [AIRGoogleMap makeGMSCameraPositionFromMap:mapView andMKCoordinateRegion:region];
|
|
[mapView animateToCameraPosition:camera];
|
|
[CATransaction commit];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(animateToCoordinate:(nonnull NSNumber *)reactTag
|
|
withRegion:(CLLocationCoordinate2D)latlng
|
|
withDuration:(CGFloat)duration)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:duration/1000];
|
|
[(AIRGoogleMap *)view animateToLocation:latlng];
|
|
[CATransaction commit];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(animateToViewingAngle:(nonnull NSNumber *)reactTag
|
|
withAngle:(double)angle
|
|
withDuration:(CGFloat)duration)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:duration/1000];
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
[mapView animateToViewingAngle:angle];
|
|
[CATransaction commit];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(animateToBearing:(nonnull NSNumber *)reactTag
|
|
withBearing:(CGFloat)bearing
|
|
withDuration:(CGFloat)duration)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:duration/1000];
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
[mapView animateToBearing:bearing];
|
|
[CATransaction commit];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(fitToElements:(nonnull NSNumber *)reactTag
|
|
animated:(BOOL)animated)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
|
|
CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(mapView.markers.firstObject)).realMarker.position;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRGoogleMapMarker *marker in mapView.markers)
|
|
bounds = [bounds includingCoordinate:marker.realMarker.position];
|
|
|
|
[mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds withPadding:55.0f]];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(fitToSuppliedMarkers:(nonnull NSNumber *)reactTag
|
|
markers:(nonnull NSArray *)markers
|
|
animated:(BOOL)animated)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
|
|
NSPredicate *filterMarkers = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
|
|
AIRGoogleMapMarker *marker = (AIRGoogleMapMarker *)evaluatedObject;
|
|
return [marker isKindOfClass:[AIRGoogleMapMarker class]] && [markers containsObject:marker.identifier];
|
|
}];
|
|
|
|
NSArray *filteredMarkers = [mapView.markers filteredArrayUsingPredicate:filterMarkers];
|
|
|
|
CLLocationCoordinate2D myLocation = ((AIRGoogleMapMarker *)(filteredMarkers.firstObject)).realMarker.position;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRGoogleMapMarker *marker in filteredMarkers)
|
|
bounds = [bounds includingCoordinate:marker.realMarker.position];
|
|
|
|
[mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds withPadding:55.0f]];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(fitToCoordinates:(nonnull NSNumber *)reactTag
|
|
coordinates:(nonnull NSArray<AIRMapCoordinate *> *)coordinates
|
|
edgePadding:(nonnull NSDictionary *)edgePadding
|
|
animated:(BOOL)animated)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
|
|
CLLocationCoordinate2D myLocation = coordinates.firstObject.coordinate;
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
|
|
|
|
for (AIRMapCoordinate *coordinate in coordinates)
|
|
bounds = [bounds includingCoordinate:coordinate.coordinate];
|
|
|
|
// Set Map viewport
|
|
CGFloat top = [RCTConvert CGFloat:edgePadding[@"top"]];
|
|
CGFloat right = [RCTConvert CGFloat:edgePadding[@"right"]];
|
|
CGFloat bottom = [RCTConvert CGFloat:edgePadding[@"bottom"]];
|
|
CGFloat left = [RCTConvert CGFloat:edgePadding[@"left"]];
|
|
|
|
[mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)]];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)reactTag
|
|
withWidth:(nonnull NSNumber *)width
|
|
withHeight:(nonnull NSNumber *)height
|
|
withRegion:(MKCoordinateRegion)region
|
|
format:(nonnull NSString *)format
|
|
quality:(nonnull NSNumber *)quality
|
|
result:(nonnull NSString *)result
|
|
withCallback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
|
|
NSString *pathComponent = [NSString stringWithFormat:@"Documents/snapshot-%.20lf.%@", timeStamp, format];
|
|
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent: pathComponent];
|
|
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
|
|
} else {
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
|
|
// TODO: currently we are ignoring width, height, region
|
|
|
|
UIGraphicsBeginImageContextWithOptions(mapView.frame.size, YES, 0.0f);
|
|
[mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
|
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
|
|
NSData *data;
|
|
if ([format isEqualToString:@"png"]) {
|
|
data = UIImagePNGRepresentation(image);
|
|
|
|
}
|
|
else if([format isEqualToString:@"jpg"]) {
|
|
data = UIImageJPEGRepresentation(image, quality.floatValue);
|
|
}
|
|
|
|
if ([result isEqualToString:@"file"]) {
|
|
[data writeToFile:filePath atomically:YES];
|
|
callback(@[[NSNull null], filePath]);
|
|
}
|
|
else if ([result isEqualToString:@"base64"]) {
|
|
callback(@[[NSNull null], [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]]);
|
|
}
|
|
else if ([result isEqualToString:@"legacy"]) {
|
|
|
|
// In the initial (iOS only) implementation of takeSnapshot,
|
|
// both the uri and the base64 encoded string were returned.
|
|
// Returning both is rarely useful and in fact causes a
|
|
// performance penalty when only the file URI is desired.
|
|
// In that case the base64 encoded string was always marshalled
|
|
// over the JS-bridge (which is quite slow).
|
|
// A new more flexible API was created to cover this.
|
|
// This code should be removed in a future release when the
|
|
// old API is fully deprecated.
|
|
[data writeToFile:filePath atomically:YES];
|
|
NSDictionary *snapshotData = @{
|
|
@"uri": filePath,
|
|
@"data": [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]
|
|
};
|
|
callback(@[[NSNull null], snapshotData]);
|
|
}
|
|
|
|
}
|
|
UIGraphicsEndImageContext();
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setMapBoundaries:(nonnull NSNumber *)reactTag
|
|
northEast:(CLLocationCoordinate2D)northEast
|
|
southWest:(CLLocationCoordinate2D)southWest)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[AIRGoogleMap class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
|
|
} else {
|
|
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
|
|
|
|
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast coordinate:southWest];
|
|
|
|
mapView.cameraTargetBounds = bounds;
|
|
}
|
|
}];
|
|
}
|
|
|
|
+ (BOOL)requiresMainQueueSetup {
|
|
return YES;
|
|
}
|
|
|
|
- (NSDictionary *)constantsToExport {
|
|
return @{ @"legalNotice": [GMSServices openSourceLicenseInfo] };
|
|
}
|
|
|
|
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
|
|
if (didCallOnMapReady) return;
|
|
didCallOnMapReady = YES;
|
|
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView didPrepareMap];
|
|
}
|
|
|
|
- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
return [googleMapView didTapMarker:marker];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSPolygon *)polygon {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView didTapPolygon:polygon];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView didTapAtCoordinate:coordinate];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView didLongPressAtCoordinate:coordinate];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView didChangeCameraPosition:position];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
|
|
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
|
|
[googleMapView idleAtCameraPosition:position];
|
|
}
|
|
|
|
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
return [aMarker.fakeMarker markerInfoWindow];}
|
|
|
|
- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
return [aMarker.fakeMarker markerInfoContents];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
[aMarker.fakeMarker didTapInfoWindowOfMarker:aMarker];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
[aMarker.fakeMarker didBeginDraggingMarker:aMarker];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
[aMarker.fakeMarker didEndDraggingMarker:aMarker];
|
|
}
|
|
|
|
- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
|
|
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
|
|
[aMarker.fakeMarker didDragMarker:aMarker];
|
|
}
|
|
@end
|